diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2 1) + { + ratio = 1; + } + + perpLength = Math.sqrt(perpX * perpX + perpY * perpY); + num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perpX /= perpLength; + perpY /= perpLength; + + perpX *= num; + perpY *= num; + + vertices[index] = point.x + perpX; + vertices[index+1] = point.y + perpY; + vertices[index+2] = point.x - perpX; + vertices[index+3] = point.y - perpY; + + lastPoint = point; + } + + this.containerUpdateTransform(); + } + } - -// constructor -Rope.prototype = Object.create(Mesh.prototype); -Rope.prototype.constructor = Rope; module.exports = Rope; - -/** - * Refreshes - * - */ -Rope.prototype.refresh = function () -{ - var points = this.points; - - // if too little points, or texture hasn't got UVs set yet just move on. - if (points.length < 1 || !this._texture._uvs) - { - return; - } - - var uvs = this.uvs; - - var indices = this.indices; - var colors = this.colors; - - var textureUvs = this._texture._uvs; - var offset = new core.Point(textureUvs.x0, textureUvs.y0); - var factor = new core.Point(textureUvs.x2 - textureUvs.x0, textureUvs.y2 - textureUvs.y0); - - uvs[0] = 0 + offset.x; - uvs[1] = 0 + offset.y; - uvs[2] = 0 + offset.x; - uvs[3] = 1 * factor.y + offset.y; - - colors[0] = 1; - colors[1] = 1; - - indices[0] = 0; - indices[1] = 1; - - var total = points.length, - point, index, amount; - - for (var i = 1; i < total; i++) - { - point = points[i]; - index = i * 4; - // time to do some smart drawing! - amount = i / (total-1); - - uvs[index] = amount * factor.x + offset.x; - uvs[index+1] = 0 + offset.y; - - uvs[index+2] = amount * factor.x + offset.x; - uvs[index+3] = 1 * factor.y + offset.y; - - index = i * 2; - colors[index] = 1; - colors[index+1] = 1; - - index = i * 2; - indices[index] = index; - indices[index + 1] = index + 1; - } - - this.dirty = true; - this.indexDirty = true; -}; - -/** - * Clear texture UVs when new texture is set - * - * @private - */ -Rope.prototype._onTextureUpdate = function () -{ - - Mesh.prototype._onTextureUpdate.call(this); - - // wait for the Rope ctor to finish before calling refresh - if (this._ready) { - this.refresh(); - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -Rope.prototype.updateTransform = function () -{ - var points = this.points; - - if (points.length < 1) - { - return; - } - - var lastPoint = points[0]; - var nextPoint; - var perpX = 0; - var perpY = 0; - - // this.count -= 0.2; - - var vertices = this.vertices; - var total = points.length, - point, index, ratio, perpLength, num; - - for (var i = 0; i < total; i++) - { - point = points[i]; - index = i * 4; - - if (i < points.length-1) - { - nextPoint = points[i+1]; - } - else - { - nextPoint = point; - } - - perpY = -(nextPoint.x - lastPoint.x); - perpX = nextPoint.y - lastPoint.y; - - ratio = (1 - (i / (total-1))) * 10; - - if (ratio > 1) - { - ratio = 1; - } - - perpLength = Math.sqrt(perpX * perpX + perpY * perpY); - num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perpX /= perpLength; - perpY /= perpLength; - - perpX *= num; - perpY *= num; - - vertices[index] = point.x + perpX; - vertices[index+1] = point.y + perpY; - vertices[index+2] = point.x - perpX; - vertices[index+3] = point.y - perpY; - - lastPoint = point; - } - - this.containerUpdateTransform(); -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2 1) + { + ratio = 1; + } + + perpLength = Math.sqrt(perpX * perpX + perpY * perpY); + num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perpX /= perpLength; + perpY /= perpLength; + + perpX *= num; + perpY *= num; + + vertices[index] = point.x + perpX; + vertices[index+1] = point.y + perpY; + vertices[index+2] = point.x - perpX; + vertices[index+3] = point.y - perpY; + + lastPoint = point; + } + + this.containerUpdateTransform(); + } + } - -// constructor -Rope.prototype = Object.create(Mesh.prototype); -Rope.prototype.constructor = Rope; module.exports = Rope; - -/** - * Refreshes - * - */ -Rope.prototype.refresh = function () -{ - var points = this.points; - - // if too little points, or texture hasn't got UVs set yet just move on. - if (points.length < 1 || !this._texture._uvs) - { - return; - } - - var uvs = this.uvs; - - var indices = this.indices; - var colors = this.colors; - - var textureUvs = this._texture._uvs; - var offset = new core.Point(textureUvs.x0, textureUvs.y0); - var factor = new core.Point(textureUvs.x2 - textureUvs.x0, textureUvs.y2 - textureUvs.y0); - - uvs[0] = 0 + offset.x; - uvs[1] = 0 + offset.y; - uvs[2] = 0 + offset.x; - uvs[3] = 1 * factor.y + offset.y; - - colors[0] = 1; - colors[1] = 1; - - indices[0] = 0; - indices[1] = 1; - - var total = points.length, - point, index, amount; - - for (var i = 1; i < total; i++) - { - point = points[i]; - index = i * 4; - // time to do some smart drawing! - amount = i / (total-1); - - uvs[index] = amount * factor.x + offset.x; - uvs[index+1] = 0 + offset.y; - - uvs[index+2] = amount * factor.x + offset.x; - uvs[index+3] = 1 * factor.y + offset.y; - - index = i * 2; - colors[index] = 1; - colors[index+1] = 1; - - index = i * 2; - indices[index] = index; - indices[index + 1] = index + 1; - } - - this.dirty = true; - this.indexDirty = true; -}; - -/** - * Clear texture UVs when new texture is set - * - * @private - */ -Rope.prototype._onTextureUpdate = function () -{ - - Mesh.prototype._onTextureUpdate.call(this); - - // wait for the Rope ctor to finish before calling refresh - if (this._ready) { - this.refresh(); - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -Rope.prototype.updateTransform = function () -{ - var points = this.points; - - if (points.length < 1) - { - return; - } - - var lastPoint = points[0]; - var nextPoint; - var perpX = 0; - var perpY = 0; - - // this.count -= 0.2; - - var vertices = this.vertices; - var total = points.length, - point, index, ratio, perpLength, num; - - for (var i = 0; i < total; i++) - { - point = points[i]; - index = i * 4; - - if (i < points.length-1) - { - nextPoint = points[i+1]; - } - else - { - nextPoint = point; - } - - perpY = -(nextPoint.x - lastPoint.x); - perpX = nextPoint.y - lastPoint.y; - - ratio = (1 - (i / (total-1))) * 10; - - if (ratio > 1) - { - ratio = 1; - } - - perpLength = Math.sqrt(perpX * perpX + perpY * perpY); - num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perpX /= perpLength; - perpY /= perpLength; - - perpX *= num; - perpY *= num; - - vertices[index] = point.x + perpX; - vertices[index+1] = point.y + perpY; - vertices[index+2] = point.x - perpX; - vertices[index+3] = point.y - perpY; - - lastPoint = point; - } - - this.containerUpdateTransform(); -}; diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index 0acda45..4fedaef 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -6,41 +6,40 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} TODO: Find a good explanation for this. */ -function MeshShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', +class MeshShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', + 'varying vec2 vTextureCoord;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - [ - 'varying vec2 vTextureCoord;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + [ + 'varying vec2 vTextureCoord;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'uniform sampler2D uSampler;', + 'uniform sampler2D uSampler;', - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', - // ' gl_FragColor = vec4(1.0);', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', + // ' gl_FragColor = vec4(1.0);', + '}' + ].join('\n') + ); + } } -MeshShader.prototype = Object.create(Shader.prototype); -MeshShader.prototype.constructor = MeshShader; module.exports = MeshShader; - diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2 1) + { + ratio = 1; + } + + perpLength = Math.sqrt(perpX * perpX + perpY * perpY); + num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perpX /= perpLength; + perpY /= perpLength; + + perpX *= num; + perpY *= num; + + vertices[index] = point.x + perpX; + vertices[index+1] = point.y + perpY; + vertices[index+2] = point.x - perpX; + vertices[index+3] = point.y - perpY; + + lastPoint = point; + } + + this.containerUpdateTransform(); + } + } - -// constructor -Rope.prototype = Object.create(Mesh.prototype); -Rope.prototype.constructor = Rope; module.exports = Rope; - -/** - * Refreshes - * - */ -Rope.prototype.refresh = function () -{ - var points = this.points; - - // if too little points, or texture hasn't got UVs set yet just move on. - if (points.length < 1 || !this._texture._uvs) - { - return; - } - - var uvs = this.uvs; - - var indices = this.indices; - var colors = this.colors; - - var textureUvs = this._texture._uvs; - var offset = new core.Point(textureUvs.x0, textureUvs.y0); - var factor = new core.Point(textureUvs.x2 - textureUvs.x0, textureUvs.y2 - textureUvs.y0); - - uvs[0] = 0 + offset.x; - uvs[1] = 0 + offset.y; - uvs[2] = 0 + offset.x; - uvs[3] = 1 * factor.y + offset.y; - - colors[0] = 1; - colors[1] = 1; - - indices[0] = 0; - indices[1] = 1; - - var total = points.length, - point, index, amount; - - for (var i = 1; i < total; i++) - { - point = points[i]; - index = i * 4; - // time to do some smart drawing! - amount = i / (total-1); - - uvs[index] = amount * factor.x + offset.x; - uvs[index+1] = 0 + offset.y; - - uvs[index+2] = amount * factor.x + offset.x; - uvs[index+3] = 1 * factor.y + offset.y; - - index = i * 2; - colors[index] = 1; - colors[index+1] = 1; - - index = i * 2; - indices[index] = index; - indices[index + 1] = index + 1; - } - - this.dirty = true; - this.indexDirty = true; -}; - -/** - * Clear texture UVs when new texture is set - * - * @private - */ -Rope.prototype._onTextureUpdate = function () -{ - - Mesh.prototype._onTextureUpdate.call(this); - - // wait for the Rope ctor to finish before calling refresh - if (this._ready) { - this.refresh(); - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -Rope.prototype.updateTransform = function () -{ - var points = this.points; - - if (points.length < 1) - { - return; - } - - var lastPoint = points[0]; - var nextPoint; - var perpX = 0; - var perpY = 0; - - // this.count -= 0.2; - - var vertices = this.vertices; - var total = points.length, - point, index, ratio, perpLength, num; - - for (var i = 0; i < total; i++) - { - point = points[i]; - index = i * 4; - - if (i < points.length-1) - { - nextPoint = points[i+1]; - } - else - { - nextPoint = point; - } - - perpY = -(nextPoint.x - lastPoint.x); - perpX = nextPoint.y - lastPoint.y; - - ratio = (1 - (i / (total-1))) * 10; - - if (ratio > 1) - { - ratio = 1; - } - - perpLength = Math.sqrt(perpX * perpX + perpY * perpY); - num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perpX /= perpLength; - perpY /= perpLength; - - perpX *= num; - perpY *= num; - - vertices[index] = point.x + perpX; - vertices[index+1] = point.y + perpY; - vertices[index+2] = point.x - perpX; - vertices[index+3] = point.y - perpY; - - lastPoint = point; - } - - this.containerUpdateTransform(); -}; diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index 0acda45..4fedaef 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -6,41 +6,40 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} TODO: Find a good explanation for this. */ -function MeshShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', +class MeshShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', + 'varying vec2 vTextureCoord;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - [ - 'varying vec2 vTextureCoord;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + [ + 'varying vec2 vTextureCoord;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'uniform sampler2D uSampler;', + 'uniform sampler2D uSampler;', - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', - // ' gl_FragColor = vec4(1.0);', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', + // ' gl_FragColor = vec4(1.0);', + '}' + ].join('\n') + ); + } } -MeshShader.prototype = Object.create(Shader.prototype); -MeshShader.prototype.constructor = MeshShader; module.exports = MeshShader; - diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 688e0a3..3b7c025 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -32,301 +32,302 @@ * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. * @param [batchSize=15000] {number} Number of particles per batch. */ -function ParticleContainer(maxSize, properties, batchSize) -{ - core.Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = core.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(core.Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) +class ParticleContainer extends core.Container { + constructor(maxSize, properties, batchSize) { - return; + super(); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); } - - if(!this.baseTexture) + /** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ + setProperties(properties) { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this); + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; } } - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + /** + * Updates the object transform for rendering + * + * @private + */ + updateTransform() { - return; + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); } - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) + /** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ + renderWebGL(renderer) { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) { - continue; + return; } - var frame = child.texture.frame; - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) + if(!this.baseTexture) { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx * renderer.resolution, - transform.ty * renderer.resolution - ); + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this); + } + } - isRotated = false; + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); + } + + /** + * Set the flag that static data should be updated to true + * + * @private + */ + onChildrenChange(smallestChildIndex) + { + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ + renderCanvas(renderer) + { + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; } - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + var frame = child.texture.frame; - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; + context.globalAlpha = this.worldAlpha * child.alpha; - } - else - { - if (!isRotated) + if (child.rotation % (Math.PI * 2) === 0) { - isRotated = true; - } + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx * renderer.resolution, + transform.ty * renderer.resolution + ); - child.displayObjectUpdateTransform(); + isRotated = false; + } - var childTransform = child.worldTransform; + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - (childTransform.tx * renderer.resolution) | 0, - (childTransform.ty * renderer.resolution) | 0 - ); + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + } else { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx * renderer.resolution, - childTransform.ty * renderer.resolution - ); + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + (childTransform.tx * renderer.resolution) | 0, + (childTransform.ty * renderer.resolution) | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx * renderer.resolution, + childTransform.ty * renderer.resolution + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; } - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); + var resolution = child.texture.baseTexture.resolution; - finalWidth = frame.width; - finalHeight = frame.height; - } - - var resolution = child.texture.baseTexture.resolution; - - context.drawImage( - child.texture.baseTexture.source, - frame.x * resolution, - frame.y * resolution, - frame.width * resolution, - frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution - ); - } -}; - -/** - * Destroys the container - * - */ -ParticleContainer.prototype.destroy = function () { - core.Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); + context.drawImage( + child.texture.baseTexture.source, + frame.x * resolution, + frame.y * resolution, + frame.width * resolution, + frame.height * resolution, + positionX * resolution, + positionY * resolution, + finalWidth * resolution, + finalHeight * resolution + ); } } - this._properties = null; - this._buffers = null; -}; + /** + * Destroys the container + * + */ + destroy() { + super.destroy(arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; + } + +} + +module.exports = ParticleContainer; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2 1) + { + ratio = 1; + } + + perpLength = Math.sqrt(perpX * perpX + perpY * perpY); + num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perpX /= perpLength; + perpY /= perpLength; + + perpX *= num; + perpY *= num; + + vertices[index] = point.x + perpX; + vertices[index+1] = point.y + perpY; + vertices[index+2] = point.x - perpX; + vertices[index+3] = point.y - perpY; + + lastPoint = point; + } + + this.containerUpdateTransform(); + } + } - -// constructor -Rope.prototype = Object.create(Mesh.prototype); -Rope.prototype.constructor = Rope; module.exports = Rope; - -/** - * Refreshes - * - */ -Rope.prototype.refresh = function () -{ - var points = this.points; - - // if too little points, or texture hasn't got UVs set yet just move on. - if (points.length < 1 || !this._texture._uvs) - { - return; - } - - var uvs = this.uvs; - - var indices = this.indices; - var colors = this.colors; - - var textureUvs = this._texture._uvs; - var offset = new core.Point(textureUvs.x0, textureUvs.y0); - var factor = new core.Point(textureUvs.x2 - textureUvs.x0, textureUvs.y2 - textureUvs.y0); - - uvs[0] = 0 + offset.x; - uvs[1] = 0 + offset.y; - uvs[2] = 0 + offset.x; - uvs[3] = 1 * factor.y + offset.y; - - colors[0] = 1; - colors[1] = 1; - - indices[0] = 0; - indices[1] = 1; - - var total = points.length, - point, index, amount; - - for (var i = 1; i < total; i++) - { - point = points[i]; - index = i * 4; - // time to do some smart drawing! - amount = i / (total-1); - - uvs[index] = amount * factor.x + offset.x; - uvs[index+1] = 0 + offset.y; - - uvs[index+2] = amount * factor.x + offset.x; - uvs[index+3] = 1 * factor.y + offset.y; - - index = i * 2; - colors[index] = 1; - colors[index+1] = 1; - - index = i * 2; - indices[index] = index; - indices[index + 1] = index + 1; - } - - this.dirty = true; - this.indexDirty = true; -}; - -/** - * Clear texture UVs when new texture is set - * - * @private - */ -Rope.prototype._onTextureUpdate = function () -{ - - Mesh.prototype._onTextureUpdate.call(this); - - // wait for the Rope ctor to finish before calling refresh - if (this._ready) { - this.refresh(); - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -Rope.prototype.updateTransform = function () -{ - var points = this.points; - - if (points.length < 1) - { - return; - } - - var lastPoint = points[0]; - var nextPoint; - var perpX = 0; - var perpY = 0; - - // this.count -= 0.2; - - var vertices = this.vertices; - var total = points.length, - point, index, ratio, perpLength, num; - - for (var i = 0; i < total; i++) - { - point = points[i]; - index = i * 4; - - if (i < points.length-1) - { - nextPoint = points[i+1]; - } - else - { - nextPoint = point; - } - - perpY = -(nextPoint.x - lastPoint.x); - perpX = nextPoint.y - lastPoint.y; - - ratio = (1 - (i / (total-1))) * 10; - - if (ratio > 1) - { - ratio = 1; - } - - perpLength = Math.sqrt(perpX * perpX + perpY * perpY); - num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perpX /= perpLength; - perpY /= perpLength; - - perpX *= num; - perpY *= num; - - vertices[index] = point.x + perpX; - vertices[index+1] = point.y + perpY; - vertices[index+2] = point.x - perpX; - vertices[index+3] = point.y - perpY; - - lastPoint = point; - } - - this.containerUpdateTransform(); -}; diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index 0acda45..4fedaef 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -6,41 +6,40 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} TODO: Find a good explanation for this. */ -function MeshShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', +class MeshShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', + 'varying vec2 vTextureCoord;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - [ - 'varying vec2 vTextureCoord;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + [ + 'varying vec2 vTextureCoord;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'uniform sampler2D uSampler;', + 'uniform sampler2D uSampler;', - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', - // ' gl_FragColor = vec4(1.0);', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', + // ' gl_FragColor = vec4(1.0);', + '}' + ].join('\n') + ); + } } -MeshShader.prototype = Object.create(Shader.prototype); -MeshShader.prototype.constructor = MeshShader; module.exports = MeshShader; - diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 688e0a3..3b7c025 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -32,301 +32,302 @@ * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. * @param [batchSize=15000] {number} Number of particles per batch. */ -function ParticleContainer(maxSize, properties, batchSize) -{ - core.Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = core.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(core.Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) +class ParticleContainer extends core.Container { + constructor(maxSize, properties, batchSize) { - return; + super(); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); } - - if(!this.baseTexture) + /** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ + setProperties(properties) { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this); + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; } } - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + /** + * Updates the object transform for rendering + * + * @private + */ + updateTransform() { - return; + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); } - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) + /** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ + renderWebGL(renderer) { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) { - continue; + return; } - var frame = child.texture.frame; - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) + if(!this.baseTexture) { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx * renderer.resolution, - transform.ty * renderer.resolution - ); + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this); + } + } - isRotated = false; + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); + } + + /** + * Set the flag that static data should be updated to true + * + * @private + */ + onChildrenChange(smallestChildIndex) + { + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ + renderCanvas(renderer) + { + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; } - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + var frame = child.texture.frame; - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; + context.globalAlpha = this.worldAlpha * child.alpha; - } - else - { - if (!isRotated) + if (child.rotation % (Math.PI * 2) === 0) { - isRotated = true; - } + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx * renderer.resolution, + transform.ty * renderer.resolution + ); - child.displayObjectUpdateTransform(); + isRotated = false; + } - var childTransform = child.worldTransform; + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - (childTransform.tx * renderer.resolution) | 0, - (childTransform.ty * renderer.resolution) | 0 - ); + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + } else { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx * renderer.resolution, - childTransform.ty * renderer.resolution - ); + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + (childTransform.tx * renderer.resolution) | 0, + (childTransform.ty * renderer.resolution) | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx * renderer.resolution, + childTransform.ty * renderer.resolution + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; } - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); + var resolution = child.texture.baseTexture.resolution; - finalWidth = frame.width; - finalHeight = frame.height; - } - - var resolution = child.texture.baseTexture.resolution; - - context.drawImage( - child.texture.baseTexture.source, - frame.x * resolution, - frame.y * resolution, - frame.width * resolution, - frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution - ); - } -}; - -/** - * Destroys the container - * - */ -ParticleContainer.prototype.destroy = function () { - core.Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); + context.drawImage( + child.texture.baseTexture.source, + frame.x * resolution, + frame.y * resolution, + frame.width * resolution, + frame.height * resolution, + positionX * resolution, + positionY * resolution, + finalWidth * resolution, + finalHeight * resolution + ); } } - this._properties = null; - this._buffers = null; -}; + /** + * Destroys the container + * + */ + destroy() { + super.destroy(arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; + } + +} + +module.exports = ParticleContainer; diff --git a/src/particles/webgl/ParticleBuffer.js b/src/particles/webgl/ParticleBuffer.js index c9c7fd5..d3ea376 100644 --- a/src/particles/webgl/ParticleBuffer.js +++ b/src/particles/webgl/ParticleBuffer.js @@ -19,211 +19,213 @@ * @private * @memberof PIXI */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) +class ParticleBuffer { + constructor(gl, properties, dynamicPropertyFlags, size) { - var property = properties[i]; + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - // Make copy of properties object so that when we edit the offset it doesn't - // change all other instances of the object literal - property = - { - attribute:property.attribute, - size:property.size, - uploadFunction:property.uploadFunction, - offset:property.offset - }; + /** + * Size of a single vertex. + * + * @member {number} + */ + this.vertSize = 2; - if(dynamicPropertyFlags[i]) + /** + * Size of a single vertex in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of particles the buffer can hold + * + * @member {number} + */ + this.size = size; + + /** + * A list of the properties that are dynamic. + * + * @member {object[]} + */ + this.dynamicProperties = []; + + /** + * A list of the properties that are static. + * + * @member {object[]} + */ + this.staticProperties = []; + + for (var i = 0; i < properties.length; i++) { - this.dynamicProperties.push(property); + var property = properties[i]; + + // Make copy of properties object so that when we edit the offset it doesn't + // change all other instances of the object literal + property = + { + attribute:property.attribute, + size:property.size, + uploadFunction:property.uploadFunction, + offset:property.offset + }; + + if(dynamicPropertyFlags[i]) + { + this.dynamicProperties.push(property); + } + else + { + this.staticProperties.push(property); + } } - else + + this.staticStride = 0; + this.staticBuffer = null; + this.staticData = null; + + this.dynamicStride = 0; + this.dynamicBuffer = null; + this.dynamicData = null; + + this.initBuffers(); + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + initBuffers() + { + var gl = this.gl; + var i; + var property; + + var dynamicOffset = 0; + + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + + this.dynamicStride = 0; + + for (i = 0; i < this.dynamicProperties.length; i++) { - this.staticProperties.push(property); + property = this.dynamicProperties[i]; + + property.offset = dynamicOffset; + dynamicOffset += property.size; + this.dynamicStride += property.size; + } + + this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); + this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); + + // static // + var staticOffset = 0; + this.staticStride = 0; + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + + property.offset = staticOffset; + staticOffset += property.size; + this.staticStride += property.size; + + + } + + this.staticData = new Float32Array( this.size * this.staticStride * 4); + this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); + + + this.vao = new glCore.VertexArrayObject(gl) + .addIndex(this.indexBuffer); + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); + } + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); } } - this.staticStride = 0; - this.staticBuffer = null; - this.staticData = null; + /** + * Uploads the dynamic properties. + * + */ + uploadDynamic(children, startIndex, amount) + { + for (var i = 0; i < this.dynamicProperties.length; i++) + { + var property = this.dynamicProperties[i]; + property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); + } - this.dynamicStride = 0; - this.dynamicBuffer = null; - this.dynamicData = null; + this.dynamicBuffer.upload(); + } - this.initBuffers(); + /** + * Uploads the static properties. + * + */ + uploadStatic(children, startIndex, amount) + { + for (var i = 0; i < this.staticProperties.length; i++) + { + var property = this.staticProperties[i]; + property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); + } + + this.staticBuffer.upload(); + } + + /** + * Binds the buffers to the GPU + * + */ + bind() + { + this.vao.bind(); + } + + /** + * Destroys the ParticleBuffer. + * + */ + destroy() + { + this.dynamicProperties = null; + this.dynamicData = null; + this.dynamicBuffer.destroy(); + + this.staticProperties = null; + this.staticData = null; + this.staticBuffer.destroy(); + } } -ParticleBuffer.prototype.constructor = ParticleBuffer; module.exports = ParticleBuffer; - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2 1) + { + ratio = 1; + } + + perpLength = Math.sqrt(perpX * perpX + perpY * perpY); + num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perpX /= perpLength; + perpY /= perpLength; + + perpX *= num; + perpY *= num; + + vertices[index] = point.x + perpX; + vertices[index+1] = point.y + perpY; + vertices[index+2] = point.x - perpX; + vertices[index+3] = point.y - perpY; + + lastPoint = point; + } + + this.containerUpdateTransform(); + } + } - -// constructor -Rope.prototype = Object.create(Mesh.prototype); -Rope.prototype.constructor = Rope; module.exports = Rope; - -/** - * Refreshes - * - */ -Rope.prototype.refresh = function () -{ - var points = this.points; - - // if too little points, or texture hasn't got UVs set yet just move on. - if (points.length < 1 || !this._texture._uvs) - { - return; - } - - var uvs = this.uvs; - - var indices = this.indices; - var colors = this.colors; - - var textureUvs = this._texture._uvs; - var offset = new core.Point(textureUvs.x0, textureUvs.y0); - var factor = new core.Point(textureUvs.x2 - textureUvs.x0, textureUvs.y2 - textureUvs.y0); - - uvs[0] = 0 + offset.x; - uvs[1] = 0 + offset.y; - uvs[2] = 0 + offset.x; - uvs[3] = 1 * factor.y + offset.y; - - colors[0] = 1; - colors[1] = 1; - - indices[0] = 0; - indices[1] = 1; - - var total = points.length, - point, index, amount; - - for (var i = 1; i < total; i++) - { - point = points[i]; - index = i * 4; - // time to do some smart drawing! - amount = i / (total-1); - - uvs[index] = amount * factor.x + offset.x; - uvs[index+1] = 0 + offset.y; - - uvs[index+2] = amount * factor.x + offset.x; - uvs[index+3] = 1 * factor.y + offset.y; - - index = i * 2; - colors[index] = 1; - colors[index+1] = 1; - - index = i * 2; - indices[index] = index; - indices[index + 1] = index + 1; - } - - this.dirty = true; - this.indexDirty = true; -}; - -/** - * Clear texture UVs when new texture is set - * - * @private - */ -Rope.prototype._onTextureUpdate = function () -{ - - Mesh.prototype._onTextureUpdate.call(this); - - // wait for the Rope ctor to finish before calling refresh - if (this._ready) { - this.refresh(); - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -Rope.prototype.updateTransform = function () -{ - var points = this.points; - - if (points.length < 1) - { - return; - } - - var lastPoint = points[0]; - var nextPoint; - var perpX = 0; - var perpY = 0; - - // this.count -= 0.2; - - var vertices = this.vertices; - var total = points.length, - point, index, ratio, perpLength, num; - - for (var i = 0; i < total; i++) - { - point = points[i]; - index = i * 4; - - if (i < points.length-1) - { - nextPoint = points[i+1]; - } - else - { - nextPoint = point; - } - - perpY = -(nextPoint.x - lastPoint.x); - perpX = nextPoint.y - lastPoint.y; - - ratio = (1 - (i / (total-1))) * 10; - - if (ratio > 1) - { - ratio = 1; - } - - perpLength = Math.sqrt(perpX * perpX + perpY * perpY); - num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perpX /= perpLength; - perpY /= perpLength; - - perpX *= num; - perpY *= num; - - vertices[index] = point.x + perpX; - vertices[index+1] = point.y + perpY; - vertices[index+2] = point.x - perpX; - vertices[index+3] = point.y - perpY; - - lastPoint = point; - } - - this.containerUpdateTransform(); -}; diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index 0acda45..4fedaef 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -6,41 +6,40 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} TODO: Find a good explanation for this. */ -function MeshShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', +class MeshShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', + 'varying vec2 vTextureCoord;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - [ - 'varying vec2 vTextureCoord;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + [ + 'varying vec2 vTextureCoord;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'uniform sampler2D uSampler;', + 'uniform sampler2D uSampler;', - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', - // ' gl_FragColor = vec4(1.0);', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', + // ' gl_FragColor = vec4(1.0);', + '}' + ].join('\n') + ); + } } -MeshShader.prototype = Object.create(Shader.prototype); -MeshShader.prototype.constructor = MeshShader; module.exports = MeshShader; - diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 688e0a3..3b7c025 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -32,301 +32,302 @@ * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. * @param [batchSize=15000] {number} Number of particles per batch. */ -function ParticleContainer(maxSize, properties, batchSize) -{ - core.Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = core.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(core.Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) +class ParticleContainer extends core.Container { + constructor(maxSize, properties, batchSize) { - return; + super(); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); } - - if(!this.baseTexture) + /** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ + setProperties(properties) { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this); + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; } } - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + /** + * Updates the object transform for rendering + * + * @private + */ + updateTransform() { - return; + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); } - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) + /** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ + renderWebGL(renderer) { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) { - continue; + return; } - var frame = child.texture.frame; - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) + if(!this.baseTexture) { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx * renderer.resolution, - transform.ty * renderer.resolution - ); + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this); + } + } - isRotated = false; + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); + } + + /** + * Set the flag that static data should be updated to true + * + * @private + */ + onChildrenChange(smallestChildIndex) + { + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ + renderCanvas(renderer) + { + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; } - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + var frame = child.texture.frame; - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; + context.globalAlpha = this.worldAlpha * child.alpha; - } - else - { - if (!isRotated) + if (child.rotation % (Math.PI * 2) === 0) { - isRotated = true; - } + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx * renderer.resolution, + transform.ty * renderer.resolution + ); - child.displayObjectUpdateTransform(); + isRotated = false; + } - var childTransform = child.worldTransform; + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - (childTransform.tx * renderer.resolution) | 0, - (childTransform.ty * renderer.resolution) | 0 - ); + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + } else { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx * renderer.resolution, - childTransform.ty * renderer.resolution - ); + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + (childTransform.tx * renderer.resolution) | 0, + (childTransform.ty * renderer.resolution) | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx * renderer.resolution, + childTransform.ty * renderer.resolution + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; } - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); + var resolution = child.texture.baseTexture.resolution; - finalWidth = frame.width; - finalHeight = frame.height; - } - - var resolution = child.texture.baseTexture.resolution; - - context.drawImage( - child.texture.baseTexture.source, - frame.x * resolution, - frame.y * resolution, - frame.width * resolution, - frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution - ); - } -}; - -/** - * Destroys the container - * - */ -ParticleContainer.prototype.destroy = function () { - core.Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); + context.drawImage( + child.texture.baseTexture.source, + frame.x * resolution, + frame.y * resolution, + frame.width * resolution, + frame.height * resolution, + positionX * resolution, + positionY * resolution, + finalWidth * resolution, + finalHeight * resolution + ); } } - this._properties = null; - this._buffers = null; -}; + /** + * Destroys the container + * + */ + destroy() { + super.destroy(arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; + } + +} + +module.exports = ParticleContainer; diff --git a/src/particles/webgl/ParticleBuffer.js b/src/particles/webgl/ParticleBuffer.js index c9c7fd5..d3ea376 100644 --- a/src/particles/webgl/ParticleBuffer.js +++ b/src/particles/webgl/ParticleBuffer.js @@ -19,211 +19,213 @@ * @private * @memberof PIXI */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) +class ParticleBuffer { + constructor(gl, properties, dynamicPropertyFlags, size) { - var property = properties[i]; + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - // Make copy of properties object so that when we edit the offset it doesn't - // change all other instances of the object literal - property = - { - attribute:property.attribute, - size:property.size, - uploadFunction:property.uploadFunction, - offset:property.offset - }; + /** + * Size of a single vertex. + * + * @member {number} + */ + this.vertSize = 2; - if(dynamicPropertyFlags[i]) + /** + * Size of a single vertex in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of particles the buffer can hold + * + * @member {number} + */ + this.size = size; + + /** + * A list of the properties that are dynamic. + * + * @member {object[]} + */ + this.dynamicProperties = []; + + /** + * A list of the properties that are static. + * + * @member {object[]} + */ + this.staticProperties = []; + + for (var i = 0; i < properties.length; i++) { - this.dynamicProperties.push(property); + var property = properties[i]; + + // Make copy of properties object so that when we edit the offset it doesn't + // change all other instances of the object literal + property = + { + attribute:property.attribute, + size:property.size, + uploadFunction:property.uploadFunction, + offset:property.offset + }; + + if(dynamicPropertyFlags[i]) + { + this.dynamicProperties.push(property); + } + else + { + this.staticProperties.push(property); + } } - else + + this.staticStride = 0; + this.staticBuffer = null; + this.staticData = null; + + this.dynamicStride = 0; + this.dynamicBuffer = null; + this.dynamicData = null; + + this.initBuffers(); + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + initBuffers() + { + var gl = this.gl; + var i; + var property; + + var dynamicOffset = 0; + + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + + this.dynamicStride = 0; + + for (i = 0; i < this.dynamicProperties.length; i++) { - this.staticProperties.push(property); + property = this.dynamicProperties[i]; + + property.offset = dynamicOffset; + dynamicOffset += property.size; + this.dynamicStride += property.size; + } + + this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); + this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); + + // static // + var staticOffset = 0; + this.staticStride = 0; + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + + property.offset = staticOffset; + staticOffset += property.size; + this.staticStride += property.size; + + + } + + this.staticData = new Float32Array( this.size * this.staticStride * 4); + this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); + + + this.vao = new glCore.VertexArrayObject(gl) + .addIndex(this.indexBuffer); + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); + } + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); } } - this.staticStride = 0; - this.staticBuffer = null; - this.staticData = null; + /** + * Uploads the dynamic properties. + * + */ + uploadDynamic(children, startIndex, amount) + { + for (var i = 0; i < this.dynamicProperties.length; i++) + { + var property = this.dynamicProperties[i]; + property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); + } - this.dynamicStride = 0; - this.dynamicBuffer = null; - this.dynamicData = null; + this.dynamicBuffer.upload(); + } - this.initBuffers(); + /** + * Uploads the static properties. + * + */ + uploadStatic(children, startIndex, amount) + { + for (var i = 0; i < this.staticProperties.length; i++) + { + var property = this.staticProperties[i]; + property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); + } + + this.staticBuffer.upload(); + } + + /** + * Binds the buffers to the GPU + * + */ + bind() + { + this.vao.bind(); + } + + /** + * Destroys the ParticleBuffer. + * + */ + destroy() + { + this.dynamicProperties = null; + this.dynamicData = null; + this.dynamicBuffer.destroy(); + + this.staticProperties = null; + this.staticData = null; + this.staticBuffer.destroy(); + } } -ParticleBuffer.prototype.constructor = ParticleBuffer; module.exports = ParticleBuffer; - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 6fb921c..5f0281a 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -20,411 +20,412 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function ParticleRenderer(renderer) -{ - core.ObjectRenderer.call(this, renderer); +class ParticleRenderer extends core.ObjectRenderer { + constructor(renderer) + { + super(renderer); - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - // var numIndices = 98304; + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + // and max number of element in the index buffer is 16384 * 6 = 98304 + // Creating a full index buffer, overhead is 98304 * 2 = 196Ko + // var numIndices = 98304; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + this.indexBuffer = null; + + this.properties = null; + + this.tempMatrix = new core.Matrix(); + + this.CONTEXT_UID = 0; + } /** - * The default shader that is used if a sprite doesn't have a more specific one. + * When there is a WebGL context change * - * @member {PIXI.Shader} + * @private */ - this.shader = null; + onContextChange() + { + var gl = this.renderer.gl; - this.indexBuffer = null; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.properties = null; + // setup default shader + this.shader = new ParticleShader(gl); - this.tempMatrix = new core.Matrix(); + this.properties = [ + // verticesData + { + attribute:this.shader.attributes.aVertexPosition, + size:2, + uploadFunction:this.uploadVertices, + offset:0 + }, + // positionData + { + attribute:this.shader.attributes.aPositionCoord, + size:2, + uploadFunction:this.uploadPosition, + offset:0 + }, + // rotationData + { + attribute:this.shader.attributes.aRotation, + size:1, + uploadFunction:this.uploadRotation, + offset:0 + }, + // uvsData + { + attribute:this.shader.attributes.aTextureCoord, + size:2, + uploadFunction:this.uploadUvs, + offset:0 + }, + // alphaData + { + attribute:this.shader.attributes.aColor, + size:1, + uploadFunction:this.uploadAlpha, + offset:0 + } + ]; - this.CONTEXT_UID = 0; + } + + /** + * Starts a new particle batch. + * + */ + start() + { + this.renderer.bindShader(this.shader); + } + + + /** + * Renders the particle container object. + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ + render(container) + { + var children = container.children, + totalChildren = children.length, + maxSize = container._maxSize, + batchSize = container._batchSize; + + if(totalChildren === 0) + { + return; + } + else if(totalChildren > maxSize) + { + totalChildren = maxSize; + } + + var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; + + if(!buffers) + { + buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); + } + + // if the uvs have not updated then no point rendering just yet! + this.renderer.setBlendMode(container.blendMode); + + var gl = this.renderer.gl; + + var m = container.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer._activeRenderTarget.projectionMatrix ); + this.shader.uniforms.projectionMatrix = m.toArray(true); + this.shader.uniforms.uAlpha = container.worldAlpha; + + + // make sure the texture is bound.. + var baseTexture = children[0]._texture.baseTexture; + + this.renderer.bindTexture(baseTexture); + + // now lets upload and render the buffers.. + for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) + { + var amount = ( totalChildren - i); + if(amount > batchSize) + { + amount = batchSize; + } + + var buffer = buffers[j]; + + // we always upload the dynamic + buffer.uploadDynamic(children, i, amount); + + // we only upload the static content when we have to! + if(container._bufferToUpdate === j) + { + buffer.uploadStatic(children, i, amount); + container._bufferToUpdate = j + 1; + } + + // bind the buffer + buffer.vao.bind() + .draw(gl.TRIANGLES, amount * 6) + .unbind(); + + // now draw those suckas! + // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); + // this.renderer.drawCount++; + } + } + + /** + * Creates one particle buffer for each child in the container we want to render and updates internal properties + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ + generateBuffers(container) + { + var gl = this.renderer.gl, + buffers = [], + size = container._maxSize, + batchSize = container._batchSize, + dynamicPropertyFlags = container._properties, + i; + + for (i = 0; i < size; i += batchSize) + { + buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); + } + + return buffers; + } + + /** + * Uploads the verticies. + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their vertices uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadVertices(children, startIndex, amount, array, stride, offset) + { + var sprite, + texture, + trim, + orig, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + trim = texture.trim; + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - sprite.anchor.y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = (orig.width ) * (1-sprite.anchor.x); + w1 = (orig.width ) * -sprite.anchor.x; + + h0 = orig.height * (1-sprite.anchor.y); + h1 = orig.height * -sprite.anchor.y; + } + + array[offset] = w1 * sx; + array[offset + 1] = h1 * sy; + + array[offset + stride] = w0 * sx; + array[offset + stride + 1] = h1 * sy; + + array[offset + stride * 2] = w0 * sx; + array[offset + stride * 2 + 1] = h0 * sy; + + array[offset + stride * 3] = w1 * sx; + array[offset + stride * 3 + 1] = h0 * sy; + + offset += stride * 4; + } + + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their positions uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadPosition(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spritePosition = children[startIndex + i].position; + + array[offset] = spritePosition.x; + array[offset + 1] = spritePosition.y; + + array[offset + stride] = spritePosition.x; + array[offset + stride + 1] = spritePosition.y; + + array[offset + stride * 2] = spritePosition.x; + array[offset + stride * 2 + 1] = spritePosition.y; + + array[offset + stride * 3] = spritePosition.x; + array[offset + stride * 3 + 1] = spritePosition.y; + + offset += stride * 4; + } + + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their rotation uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadRotation(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spriteRotation = children[startIndex + i].rotation; + + + array[offset] = spriteRotation; + array[offset + stride] = spriteRotation; + array[offset + stride * 2] = spriteRotation; + array[offset + stride * 3] = spriteRotation; + + offset += stride * 4; + } + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their Uvs uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadUvs(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var textureUvs = children[startIndex + i]._texture._uvs; + + if (textureUvs) + { + array[offset] = textureUvs.x0; + array[offset + 1] = textureUvs.y0; + + array[offset + stride] = textureUvs.x1; + array[offset + stride + 1] = textureUvs.y1; + + array[offset + stride * 2] = textureUvs.x2; + array[offset + stride * 2 + 1] = textureUvs.y2; + + array[offset + stride * 3] = textureUvs.x3; + array[offset + stride * 3 + 1] = textureUvs.y3; + + offset += stride * 4; + } + else + { + //TODO you know this can be easier! + array[offset] = 0; + array[offset + 1] = 0; + + array[offset + stride] = 0; + array[offset + stride + 1] = 0; + + array[offset + stride * 2] = 0; + array[offset + stride * 2 + 1] = 0; + + array[offset + stride * 3] = 0; + array[offset + stride * 3 + 1] = 0; + + offset += stride * 4; + } + } + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their alpha uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadAlpha(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spriteAlpha = children[startIndex + i].alpha; + + array[offset] = spriteAlpha; + array[offset + stride] = spriteAlpha; + array[offset + stride * 2] = spriteAlpha; + array[offset + stride * 3] = spriteAlpha; + + offset += stride * 4; + } + } + + + /** + * Destroys the ParticleRenderer. + * + */ + destroy() + { + if (this.renderer.gl) { + this.renderer.gl.deleteBuffer(this.indexBuffer); + } + core.ObjectRenderer.prototype.destroy.apply(this, arguments); + + this.shader.destroy(); + + this.indices = null; + this.tempMatrix = null; + } + } -ParticleRenderer.prototype = Object.create(core.ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; module.exports = ParticleRenderer; core.WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - var m = container.worldTransform.copy( this.tempMatrix ); - m.prepend( this.renderer._activeRenderTarget.projectionMatrix ); - this.shader.uniforms.projectionMatrix = m.toArray(true); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind() - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = function (children, startIndex, amount, array, stride, offset) -{ - var sprite, - texture, - trim, - orig, - sx, - sy, - w0, w1, h0, h1; - - for (var i = 0; i < amount; i++) { - - sprite = children[startIndex + i]; - texture = sprite._texture; - sx = sprite.scale.x; - sy = sprite.scale.y; - trim = texture.trim; - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (orig.width ) * (1-sprite.anchor.x); - w1 = (orig.width ) * -sprite.anchor.x; - - h0 = orig.height * (1-sprite.anchor.y); - h1 = orig.height * -sprite.anchor.y; - } - - array[offset] = w1 * sx; - array[offset + 1] = h1 * sy; - - array[offset + stride] = w0 * sx; - array[offset + stride + 1] = h1 * sy; - - array[offset + stride * 2] = w0 * sx; - array[offset + stride * 2 + 1] = h0 * sy; - - array[offset + stride * 3] = w1 * sx; - array[offset + stride * 3 + 1] = h0 * sy; - - offset += stride * 4; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadPosition = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spritePosition = children[startIndex + i].position; - - array[offset] = spritePosition.x; - array[offset + 1] = spritePosition.y; - - array[offset + stride] = spritePosition.x; - array[offset + stride + 1] = spritePosition.y; - - array[offset + stride * 2] = spritePosition.x; - array[offset + stride * 2 + 1] = spritePosition.y; - - array[offset + stride * 3] = spritePosition.x; - array[offset + stride * 3 + 1] = spritePosition.y; - - offset += stride * 4; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadRotation = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spriteRotation = children[startIndex + i].rotation; - - - array[offset] = spriteRotation; - array[offset + stride] = spriteRotation; - array[offset + stride * 2] = spriteRotation; - array[offset + stride * 3] = spriteRotation; - - offset += stride * 4; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadUvs = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var textureUvs = children[startIndex + i]._texture._uvs; - - if (textureUvs) - { - array[offset] = textureUvs.x0; - array[offset + 1] = textureUvs.y0; - - array[offset + stride] = textureUvs.x1; - array[offset + stride + 1] = textureUvs.y1; - - array[offset + stride * 2] = textureUvs.x2; - array[offset + stride * 2 + 1] = textureUvs.y2; - - array[offset + stride * 3] = textureUvs.x3; - array[offset + stride * 3 + 1] = textureUvs.y3; - - offset += stride * 4; - } - else - { - //TODO you know this can be easier! - array[offset] = 0; - array[offset + 1] = 0; - - array[offset + stride] = 0; - array[offset + stride + 1] = 0; - - array[offset + stride * 2] = 0; - array[offset + stride * 2 + 1] = 0; - - array[offset + stride * 3] = 0; - array[offset + stride * 3 + 1] = 0; - - offset += stride * 4; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadAlpha = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spriteAlpha = children[startIndex + i].alpha; - - array[offset] = spriteAlpha; - array[offset + stride] = spriteAlpha; - array[offset + stride * 2] = spriteAlpha; - array[offset + stride * 3] = spriteAlpha; - - offset += stride * 4; - } -}; - - -/** - * Destroys the ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - core.ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2 1) + { + ratio = 1; + } + + perpLength = Math.sqrt(perpX * perpX + perpY * perpY); + num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perpX /= perpLength; + perpY /= perpLength; + + perpX *= num; + perpY *= num; + + vertices[index] = point.x + perpX; + vertices[index+1] = point.y + perpY; + vertices[index+2] = point.x - perpX; + vertices[index+3] = point.y - perpY; + + lastPoint = point; + } + + this.containerUpdateTransform(); + } + } - -// constructor -Rope.prototype = Object.create(Mesh.prototype); -Rope.prototype.constructor = Rope; module.exports = Rope; - -/** - * Refreshes - * - */ -Rope.prototype.refresh = function () -{ - var points = this.points; - - // if too little points, or texture hasn't got UVs set yet just move on. - if (points.length < 1 || !this._texture._uvs) - { - return; - } - - var uvs = this.uvs; - - var indices = this.indices; - var colors = this.colors; - - var textureUvs = this._texture._uvs; - var offset = new core.Point(textureUvs.x0, textureUvs.y0); - var factor = new core.Point(textureUvs.x2 - textureUvs.x0, textureUvs.y2 - textureUvs.y0); - - uvs[0] = 0 + offset.x; - uvs[1] = 0 + offset.y; - uvs[2] = 0 + offset.x; - uvs[3] = 1 * factor.y + offset.y; - - colors[0] = 1; - colors[1] = 1; - - indices[0] = 0; - indices[1] = 1; - - var total = points.length, - point, index, amount; - - for (var i = 1; i < total; i++) - { - point = points[i]; - index = i * 4; - // time to do some smart drawing! - amount = i / (total-1); - - uvs[index] = amount * factor.x + offset.x; - uvs[index+1] = 0 + offset.y; - - uvs[index+2] = amount * factor.x + offset.x; - uvs[index+3] = 1 * factor.y + offset.y; - - index = i * 2; - colors[index] = 1; - colors[index+1] = 1; - - index = i * 2; - indices[index] = index; - indices[index + 1] = index + 1; - } - - this.dirty = true; - this.indexDirty = true; -}; - -/** - * Clear texture UVs when new texture is set - * - * @private - */ -Rope.prototype._onTextureUpdate = function () -{ - - Mesh.prototype._onTextureUpdate.call(this); - - // wait for the Rope ctor to finish before calling refresh - if (this._ready) { - this.refresh(); - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -Rope.prototype.updateTransform = function () -{ - var points = this.points; - - if (points.length < 1) - { - return; - } - - var lastPoint = points[0]; - var nextPoint; - var perpX = 0; - var perpY = 0; - - // this.count -= 0.2; - - var vertices = this.vertices; - var total = points.length, - point, index, ratio, perpLength, num; - - for (var i = 0; i < total; i++) - { - point = points[i]; - index = i * 4; - - if (i < points.length-1) - { - nextPoint = points[i+1]; - } - else - { - nextPoint = point; - } - - perpY = -(nextPoint.x - lastPoint.x); - perpX = nextPoint.y - lastPoint.y; - - ratio = (1 - (i / (total-1))) * 10; - - if (ratio > 1) - { - ratio = 1; - } - - perpLength = Math.sqrt(perpX * perpX + perpY * perpY); - num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perpX /= perpLength; - perpY /= perpLength; - - perpX *= num; - perpY *= num; - - vertices[index] = point.x + perpX; - vertices[index+1] = point.y + perpY; - vertices[index+2] = point.x - perpX; - vertices[index+3] = point.y - perpY; - - lastPoint = point; - } - - this.containerUpdateTransform(); -}; diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index 0acda45..4fedaef 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -6,41 +6,40 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} TODO: Find a good explanation for this. */ -function MeshShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', +class MeshShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', + 'varying vec2 vTextureCoord;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - [ - 'varying vec2 vTextureCoord;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + [ + 'varying vec2 vTextureCoord;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'uniform sampler2D uSampler;', + 'uniform sampler2D uSampler;', - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', - // ' gl_FragColor = vec4(1.0);', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', + // ' gl_FragColor = vec4(1.0);', + '}' + ].join('\n') + ); + } } -MeshShader.prototype = Object.create(Shader.prototype); -MeshShader.prototype.constructor = MeshShader; module.exports = MeshShader; - diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 688e0a3..3b7c025 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -32,301 +32,302 @@ * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. * @param [batchSize=15000] {number} Number of particles per batch. */ -function ParticleContainer(maxSize, properties, batchSize) -{ - core.Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = core.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(core.Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) +class ParticleContainer extends core.Container { + constructor(maxSize, properties, batchSize) { - return; + super(); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); } - - if(!this.baseTexture) + /** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ + setProperties(properties) { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this); + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; } } - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + /** + * Updates the object transform for rendering + * + * @private + */ + updateTransform() { - return; + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); } - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) + /** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ + renderWebGL(renderer) { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) { - continue; + return; } - var frame = child.texture.frame; - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) + if(!this.baseTexture) { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx * renderer.resolution, - transform.ty * renderer.resolution - ); + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this); + } + } - isRotated = false; + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); + } + + /** + * Set the flag that static data should be updated to true + * + * @private + */ + onChildrenChange(smallestChildIndex) + { + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ + renderCanvas(renderer) + { + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; } - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + var frame = child.texture.frame; - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; + context.globalAlpha = this.worldAlpha * child.alpha; - } - else - { - if (!isRotated) + if (child.rotation % (Math.PI * 2) === 0) { - isRotated = true; - } + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx * renderer.resolution, + transform.ty * renderer.resolution + ); - child.displayObjectUpdateTransform(); + isRotated = false; + } - var childTransform = child.worldTransform; + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - (childTransform.tx * renderer.resolution) | 0, - (childTransform.ty * renderer.resolution) | 0 - ); + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + } else { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx * renderer.resolution, - childTransform.ty * renderer.resolution - ); + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + (childTransform.tx * renderer.resolution) | 0, + (childTransform.ty * renderer.resolution) | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx * renderer.resolution, + childTransform.ty * renderer.resolution + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; } - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); + var resolution = child.texture.baseTexture.resolution; - finalWidth = frame.width; - finalHeight = frame.height; - } - - var resolution = child.texture.baseTexture.resolution; - - context.drawImage( - child.texture.baseTexture.source, - frame.x * resolution, - frame.y * resolution, - frame.width * resolution, - frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution - ); - } -}; - -/** - * Destroys the container - * - */ -ParticleContainer.prototype.destroy = function () { - core.Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); + context.drawImage( + child.texture.baseTexture.source, + frame.x * resolution, + frame.y * resolution, + frame.width * resolution, + frame.height * resolution, + positionX * resolution, + positionY * resolution, + finalWidth * resolution, + finalHeight * resolution + ); } } - this._properties = null; - this._buffers = null; -}; + /** + * Destroys the container + * + */ + destroy() { + super.destroy(arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; + } + +} + +module.exports = ParticleContainer; diff --git a/src/particles/webgl/ParticleBuffer.js b/src/particles/webgl/ParticleBuffer.js index c9c7fd5..d3ea376 100644 --- a/src/particles/webgl/ParticleBuffer.js +++ b/src/particles/webgl/ParticleBuffer.js @@ -19,211 +19,213 @@ * @private * @memberof PIXI */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) +class ParticleBuffer { + constructor(gl, properties, dynamicPropertyFlags, size) { - var property = properties[i]; + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - // Make copy of properties object so that when we edit the offset it doesn't - // change all other instances of the object literal - property = - { - attribute:property.attribute, - size:property.size, - uploadFunction:property.uploadFunction, - offset:property.offset - }; + /** + * Size of a single vertex. + * + * @member {number} + */ + this.vertSize = 2; - if(dynamicPropertyFlags[i]) + /** + * Size of a single vertex in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of particles the buffer can hold + * + * @member {number} + */ + this.size = size; + + /** + * A list of the properties that are dynamic. + * + * @member {object[]} + */ + this.dynamicProperties = []; + + /** + * A list of the properties that are static. + * + * @member {object[]} + */ + this.staticProperties = []; + + for (var i = 0; i < properties.length; i++) { - this.dynamicProperties.push(property); + var property = properties[i]; + + // Make copy of properties object so that when we edit the offset it doesn't + // change all other instances of the object literal + property = + { + attribute:property.attribute, + size:property.size, + uploadFunction:property.uploadFunction, + offset:property.offset + }; + + if(dynamicPropertyFlags[i]) + { + this.dynamicProperties.push(property); + } + else + { + this.staticProperties.push(property); + } } - else + + this.staticStride = 0; + this.staticBuffer = null; + this.staticData = null; + + this.dynamicStride = 0; + this.dynamicBuffer = null; + this.dynamicData = null; + + this.initBuffers(); + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + initBuffers() + { + var gl = this.gl; + var i; + var property; + + var dynamicOffset = 0; + + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + + this.dynamicStride = 0; + + for (i = 0; i < this.dynamicProperties.length; i++) { - this.staticProperties.push(property); + property = this.dynamicProperties[i]; + + property.offset = dynamicOffset; + dynamicOffset += property.size; + this.dynamicStride += property.size; + } + + this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); + this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); + + // static // + var staticOffset = 0; + this.staticStride = 0; + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + + property.offset = staticOffset; + staticOffset += property.size; + this.staticStride += property.size; + + + } + + this.staticData = new Float32Array( this.size * this.staticStride * 4); + this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); + + + this.vao = new glCore.VertexArrayObject(gl) + .addIndex(this.indexBuffer); + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); + } + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); } } - this.staticStride = 0; - this.staticBuffer = null; - this.staticData = null; + /** + * Uploads the dynamic properties. + * + */ + uploadDynamic(children, startIndex, amount) + { + for (var i = 0; i < this.dynamicProperties.length; i++) + { + var property = this.dynamicProperties[i]; + property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); + } - this.dynamicStride = 0; - this.dynamicBuffer = null; - this.dynamicData = null; + this.dynamicBuffer.upload(); + } - this.initBuffers(); + /** + * Uploads the static properties. + * + */ + uploadStatic(children, startIndex, amount) + { + for (var i = 0; i < this.staticProperties.length; i++) + { + var property = this.staticProperties[i]; + property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); + } + + this.staticBuffer.upload(); + } + + /** + * Binds the buffers to the GPU + * + */ + bind() + { + this.vao.bind(); + } + + /** + * Destroys the ParticleBuffer. + * + */ + destroy() + { + this.dynamicProperties = null; + this.dynamicData = null; + this.dynamicBuffer.destroy(); + + this.staticProperties = null; + this.staticData = null; + this.staticBuffer.destroy(); + } } -ParticleBuffer.prototype.constructor = ParticleBuffer; module.exports = ParticleBuffer; - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 6fb921c..5f0281a 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -20,411 +20,412 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function ParticleRenderer(renderer) -{ - core.ObjectRenderer.call(this, renderer); +class ParticleRenderer extends core.ObjectRenderer { + constructor(renderer) + { + super(renderer); - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - // var numIndices = 98304; + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + // and max number of element in the index buffer is 16384 * 6 = 98304 + // Creating a full index buffer, overhead is 98304 * 2 = 196Ko + // var numIndices = 98304; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + this.indexBuffer = null; + + this.properties = null; + + this.tempMatrix = new core.Matrix(); + + this.CONTEXT_UID = 0; + } /** - * The default shader that is used if a sprite doesn't have a more specific one. + * When there is a WebGL context change * - * @member {PIXI.Shader} + * @private */ - this.shader = null; + onContextChange() + { + var gl = this.renderer.gl; - this.indexBuffer = null; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.properties = null; + // setup default shader + this.shader = new ParticleShader(gl); - this.tempMatrix = new core.Matrix(); + this.properties = [ + // verticesData + { + attribute:this.shader.attributes.aVertexPosition, + size:2, + uploadFunction:this.uploadVertices, + offset:0 + }, + // positionData + { + attribute:this.shader.attributes.aPositionCoord, + size:2, + uploadFunction:this.uploadPosition, + offset:0 + }, + // rotationData + { + attribute:this.shader.attributes.aRotation, + size:1, + uploadFunction:this.uploadRotation, + offset:0 + }, + // uvsData + { + attribute:this.shader.attributes.aTextureCoord, + size:2, + uploadFunction:this.uploadUvs, + offset:0 + }, + // alphaData + { + attribute:this.shader.attributes.aColor, + size:1, + uploadFunction:this.uploadAlpha, + offset:0 + } + ]; - this.CONTEXT_UID = 0; + } + + /** + * Starts a new particle batch. + * + */ + start() + { + this.renderer.bindShader(this.shader); + } + + + /** + * Renders the particle container object. + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ + render(container) + { + var children = container.children, + totalChildren = children.length, + maxSize = container._maxSize, + batchSize = container._batchSize; + + if(totalChildren === 0) + { + return; + } + else if(totalChildren > maxSize) + { + totalChildren = maxSize; + } + + var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; + + if(!buffers) + { + buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); + } + + // if the uvs have not updated then no point rendering just yet! + this.renderer.setBlendMode(container.blendMode); + + var gl = this.renderer.gl; + + var m = container.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer._activeRenderTarget.projectionMatrix ); + this.shader.uniforms.projectionMatrix = m.toArray(true); + this.shader.uniforms.uAlpha = container.worldAlpha; + + + // make sure the texture is bound.. + var baseTexture = children[0]._texture.baseTexture; + + this.renderer.bindTexture(baseTexture); + + // now lets upload and render the buffers.. + for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) + { + var amount = ( totalChildren - i); + if(amount > batchSize) + { + amount = batchSize; + } + + var buffer = buffers[j]; + + // we always upload the dynamic + buffer.uploadDynamic(children, i, amount); + + // we only upload the static content when we have to! + if(container._bufferToUpdate === j) + { + buffer.uploadStatic(children, i, amount); + container._bufferToUpdate = j + 1; + } + + // bind the buffer + buffer.vao.bind() + .draw(gl.TRIANGLES, amount * 6) + .unbind(); + + // now draw those suckas! + // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); + // this.renderer.drawCount++; + } + } + + /** + * Creates one particle buffer for each child in the container we want to render and updates internal properties + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ + generateBuffers(container) + { + var gl = this.renderer.gl, + buffers = [], + size = container._maxSize, + batchSize = container._batchSize, + dynamicPropertyFlags = container._properties, + i; + + for (i = 0; i < size; i += batchSize) + { + buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); + } + + return buffers; + } + + /** + * Uploads the verticies. + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their vertices uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadVertices(children, startIndex, amount, array, stride, offset) + { + var sprite, + texture, + trim, + orig, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + trim = texture.trim; + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - sprite.anchor.y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = (orig.width ) * (1-sprite.anchor.x); + w1 = (orig.width ) * -sprite.anchor.x; + + h0 = orig.height * (1-sprite.anchor.y); + h1 = orig.height * -sprite.anchor.y; + } + + array[offset] = w1 * sx; + array[offset + 1] = h1 * sy; + + array[offset + stride] = w0 * sx; + array[offset + stride + 1] = h1 * sy; + + array[offset + stride * 2] = w0 * sx; + array[offset + stride * 2 + 1] = h0 * sy; + + array[offset + stride * 3] = w1 * sx; + array[offset + stride * 3 + 1] = h0 * sy; + + offset += stride * 4; + } + + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their positions uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadPosition(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spritePosition = children[startIndex + i].position; + + array[offset] = spritePosition.x; + array[offset + 1] = spritePosition.y; + + array[offset + stride] = spritePosition.x; + array[offset + stride + 1] = spritePosition.y; + + array[offset + stride * 2] = spritePosition.x; + array[offset + stride * 2 + 1] = spritePosition.y; + + array[offset + stride * 3] = spritePosition.x; + array[offset + stride * 3 + 1] = spritePosition.y; + + offset += stride * 4; + } + + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their rotation uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadRotation(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spriteRotation = children[startIndex + i].rotation; + + + array[offset] = spriteRotation; + array[offset + stride] = spriteRotation; + array[offset + stride * 2] = spriteRotation; + array[offset + stride * 3] = spriteRotation; + + offset += stride * 4; + } + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their Uvs uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadUvs(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var textureUvs = children[startIndex + i]._texture._uvs; + + if (textureUvs) + { + array[offset] = textureUvs.x0; + array[offset + 1] = textureUvs.y0; + + array[offset + stride] = textureUvs.x1; + array[offset + stride + 1] = textureUvs.y1; + + array[offset + stride * 2] = textureUvs.x2; + array[offset + stride * 2 + 1] = textureUvs.y2; + + array[offset + stride * 3] = textureUvs.x3; + array[offset + stride * 3 + 1] = textureUvs.y3; + + offset += stride * 4; + } + else + { + //TODO you know this can be easier! + array[offset] = 0; + array[offset + 1] = 0; + + array[offset + stride] = 0; + array[offset + stride + 1] = 0; + + array[offset + stride * 2] = 0; + array[offset + stride * 2 + 1] = 0; + + array[offset + stride * 3] = 0; + array[offset + stride * 3 + 1] = 0; + + offset += stride * 4; + } + } + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their alpha uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadAlpha(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spriteAlpha = children[startIndex + i].alpha; + + array[offset] = spriteAlpha; + array[offset + stride] = spriteAlpha; + array[offset + stride * 2] = spriteAlpha; + array[offset + stride * 3] = spriteAlpha; + + offset += stride * 4; + } + } + + + /** + * Destroys the ParticleRenderer. + * + */ + destroy() + { + if (this.renderer.gl) { + this.renderer.gl.deleteBuffer(this.indexBuffer); + } + core.ObjectRenderer.prototype.destroy.apply(this, arguments); + + this.shader.destroy(); + + this.indices = null; + this.tempMatrix = null; + } + } -ParticleRenderer.prototype = Object.create(core.ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; module.exports = ParticleRenderer; core.WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - var m = container.worldTransform.copy( this.tempMatrix ); - m.prepend( this.renderer._activeRenderTarget.projectionMatrix ); - this.shader.uniforms.projectionMatrix = m.toArray(true); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind() - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = function (children, startIndex, amount, array, stride, offset) -{ - var sprite, - texture, - trim, - orig, - sx, - sy, - w0, w1, h0, h1; - - for (var i = 0; i < amount; i++) { - - sprite = children[startIndex + i]; - texture = sprite._texture; - sx = sprite.scale.x; - sy = sprite.scale.y; - trim = texture.trim; - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (orig.width ) * (1-sprite.anchor.x); - w1 = (orig.width ) * -sprite.anchor.x; - - h0 = orig.height * (1-sprite.anchor.y); - h1 = orig.height * -sprite.anchor.y; - } - - array[offset] = w1 * sx; - array[offset + 1] = h1 * sy; - - array[offset + stride] = w0 * sx; - array[offset + stride + 1] = h1 * sy; - - array[offset + stride * 2] = w0 * sx; - array[offset + stride * 2 + 1] = h0 * sy; - - array[offset + stride * 3] = w1 * sx; - array[offset + stride * 3 + 1] = h0 * sy; - - offset += stride * 4; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadPosition = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spritePosition = children[startIndex + i].position; - - array[offset] = spritePosition.x; - array[offset + 1] = spritePosition.y; - - array[offset + stride] = spritePosition.x; - array[offset + stride + 1] = spritePosition.y; - - array[offset + stride * 2] = spritePosition.x; - array[offset + stride * 2 + 1] = spritePosition.y; - - array[offset + stride * 3] = spritePosition.x; - array[offset + stride * 3 + 1] = spritePosition.y; - - offset += stride * 4; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadRotation = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spriteRotation = children[startIndex + i].rotation; - - - array[offset] = spriteRotation; - array[offset + stride] = spriteRotation; - array[offset + stride * 2] = spriteRotation; - array[offset + stride * 3] = spriteRotation; - - offset += stride * 4; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadUvs = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var textureUvs = children[startIndex + i]._texture._uvs; - - if (textureUvs) - { - array[offset] = textureUvs.x0; - array[offset + 1] = textureUvs.y0; - - array[offset + stride] = textureUvs.x1; - array[offset + stride + 1] = textureUvs.y1; - - array[offset + stride * 2] = textureUvs.x2; - array[offset + stride * 2 + 1] = textureUvs.y2; - - array[offset + stride * 3] = textureUvs.x3; - array[offset + stride * 3 + 1] = textureUvs.y3; - - offset += stride * 4; - } - else - { - //TODO you know this can be easier! - array[offset] = 0; - array[offset + 1] = 0; - - array[offset + stride] = 0; - array[offset + stride + 1] = 0; - - array[offset + stride * 2] = 0; - array[offset + stride * 2 + 1] = 0; - - array[offset + stride * 3] = 0; - array[offset + stride * 3 + 1] = 0; - - offset += stride * 4; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadAlpha = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spriteAlpha = children[startIndex + i].alpha; - - array[offset] = spriteAlpha; - array[offset + stride] = spriteAlpha; - array[offset + stride * 2] = spriteAlpha; - array[offset + stride * 3] = spriteAlpha; - - offset += stride * 4; - } -}; - - -/** - * Destroys the ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - core.ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 4c98aab..12eefb7 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -6,59 +6,58 @@ * @memberof PIXI * @param gl {PIXI.Shader} The webgl shader manager this shader works for. */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', +class ParticleShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', + 'attribute vec2 aPositionCoord;', + 'attribute vec2 aScale;', + 'attribute float aRotation;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', - 'varying float vColor;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', - 'void main(void){', - ' vec2 v = aVertexPosition;', + 'void main(void){', + ' vec2 v = aVertexPosition;', - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', + ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', + ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', + ' v = v + aPositionCoord;', - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', + ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'varying vec2 vTextureCoord;', - 'varying float vColor;', + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' + ].join('\n'), + // hello + [ + 'varying vec2 vTextureCoord;', + 'varying float vColor;', - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', + 'uniform sampler2D uSampler;', + 'uniform float uAlpha;', - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); + 'void main(void){', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' if (color.a == 0.0) discard;', + ' gl_FragColor = color;', + '}' + ].join('\n') + ); - // TEMP HACK + // TEMP HACK + } } -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - module.exports = ParticleShader; diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2 1) + { + ratio = 1; + } + + perpLength = Math.sqrt(perpX * perpX + perpY * perpY); + num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perpX /= perpLength; + perpY /= perpLength; + + perpX *= num; + perpY *= num; + + vertices[index] = point.x + perpX; + vertices[index+1] = point.y + perpY; + vertices[index+2] = point.x - perpX; + vertices[index+3] = point.y - perpY; + + lastPoint = point; + } + + this.containerUpdateTransform(); + } + } - -// constructor -Rope.prototype = Object.create(Mesh.prototype); -Rope.prototype.constructor = Rope; module.exports = Rope; - -/** - * Refreshes - * - */ -Rope.prototype.refresh = function () -{ - var points = this.points; - - // if too little points, or texture hasn't got UVs set yet just move on. - if (points.length < 1 || !this._texture._uvs) - { - return; - } - - var uvs = this.uvs; - - var indices = this.indices; - var colors = this.colors; - - var textureUvs = this._texture._uvs; - var offset = new core.Point(textureUvs.x0, textureUvs.y0); - var factor = new core.Point(textureUvs.x2 - textureUvs.x0, textureUvs.y2 - textureUvs.y0); - - uvs[0] = 0 + offset.x; - uvs[1] = 0 + offset.y; - uvs[2] = 0 + offset.x; - uvs[3] = 1 * factor.y + offset.y; - - colors[0] = 1; - colors[1] = 1; - - indices[0] = 0; - indices[1] = 1; - - var total = points.length, - point, index, amount; - - for (var i = 1; i < total; i++) - { - point = points[i]; - index = i * 4; - // time to do some smart drawing! - amount = i / (total-1); - - uvs[index] = amount * factor.x + offset.x; - uvs[index+1] = 0 + offset.y; - - uvs[index+2] = amount * factor.x + offset.x; - uvs[index+3] = 1 * factor.y + offset.y; - - index = i * 2; - colors[index] = 1; - colors[index+1] = 1; - - index = i * 2; - indices[index] = index; - indices[index + 1] = index + 1; - } - - this.dirty = true; - this.indexDirty = true; -}; - -/** - * Clear texture UVs when new texture is set - * - * @private - */ -Rope.prototype._onTextureUpdate = function () -{ - - Mesh.prototype._onTextureUpdate.call(this); - - // wait for the Rope ctor to finish before calling refresh - if (this._ready) { - this.refresh(); - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -Rope.prototype.updateTransform = function () -{ - var points = this.points; - - if (points.length < 1) - { - return; - } - - var lastPoint = points[0]; - var nextPoint; - var perpX = 0; - var perpY = 0; - - // this.count -= 0.2; - - var vertices = this.vertices; - var total = points.length, - point, index, ratio, perpLength, num; - - for (var i = 0; i < total; i++) - { - point = points[i]; - index = i * 4; - - if (i < points.length-1) - { - nextPoint = points[i+1]; - } - else - { - nextPoint = point; - } - - perpY = -(nextPoint.x - lastPoint.x); - perpX = nextPoint.y - lastPoint.y; - - ratio = (1 - (i / (total-1))) * 10; - - if (ratio > 1) - { - ratio = 1; - } - - perpLength = Math.sqrt(perpX * perpX + perpY * perpY); - num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perpX /= perpLength; - perpY /= perpLength; - - perpX *= num; - perpY *= num; - - vertices[index] = point.x + perpX; - vertices[index+1] = point.y + perpY; - vertices[index+2] = point.x - perpX; - vertices[index+3] = point.y - perpY; - - lastPoint = point; - } - - this.containerUpdateTransform(); -}; diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index 0acda45..4fedaef 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -6,41 +6,40 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} TODO: Find a good explanation for this. */ -function MeshShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', +class MeshShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', + 'varying vec2 vTextureCoord;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - [ - 'varying vec2 vTextureCoord;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + [ + 'varying vec2 vTextureCoord;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'uniform sampler2D uSampler;', + 'uniform sampler2D uSampler;', - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', - // ' gl_FragColor = vec4(1.0);', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', + // ' gl_FragColor = vec4(1.0);', + '}' + ].join('\n') + ); + } } -MeshShader.prototype = Object.create(Shader.prototype); -MeshShader.prototype.constructor = MeshShader; module.exports = MeshShader; - diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 688e0a3..3b7c025 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -32,301 +32,302 @@ * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. * @param [batchSize=15000] {number} Number of particles per batch. */ -function ParticleContainer(maxSize, properties, batchSize) -{ - core.Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = core.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(core.Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) +class ParticleContainer extends core.Container { + constructor(maxSize, properties, batchSize) { - return; + super(); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); } - - if(!this.baseTexture) + /** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ + setProperties(properties) { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this); + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; } } - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + /** + * Updates the object transform for rendering + * + * @private + */ + updateTransform() { - return; + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); } - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) + /** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ + renderWebGL(renderer) { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) { - continue; + return; } - var frame = child.texture.frame; - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) + if(!this.baseTexture) { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx * renderer.resolution, - transform.ty * renderer.resolution - ); + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this); + } + } - isRotated = false; + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); + } + + /** + * Set the flag that static data should be updated to true + * + * @private + */ + onChildrenChange(smallestChildIndex) + { + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ + renderCanvas(renderer) + { + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; } - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + var frame = child.texture.frame; - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; + context.globalAlpha = this.worldAlpha * child.alpha; - } - else - { - if (!isRotated) + if (child.rotation % (Math.PI * 2) === 0) { - isRotated = true; - } + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx * renderer.resolution, + transform.ty * renderer.resolution + ); - child.displayObjectUpdateTransform(); + isRotated = false; + } - var childTransform = child.worldTransform; + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - (childTransform.tx * renderer.resolution) | 0, - (childTransform.ty * renderer.resolution) | 0 - ); + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + } else { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx * renderer.resolution, - childTransform.ty * renderer.resolution - ); + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + (childTransform.tx * renderer.resolution) | 0, + (childTransform.ty * renderer.resolution) | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx * renderer.resolution, + childTransform.ty * renderer.resolution + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; } - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); + var resolution = child.texture.baseTexture.resolution; - finalWidth = frame.width; - finalHeight = frame.height; - } - - var resolution = child.texture.baseTexture.resolution; - - context.drawImage( - child.texture.baseTexture.source, - frame.x * resolution, - frame.y * resolution, - frame.width * resolution, - frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution - ); - } -}; - -/** - * Destroys the container - * - */ -ParticleContainer.prototype.destroy = function () { - core.Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); + context.drawImage( + child.texture.baseTexture.source, + frame.x * resolution, + frame.y * resolution, + frame.width * resolution, + frame.height * resolution, + positionX * resolution, + positionY * resolution, + finalWidth * resolution, + finalHeight * resolution + ); } } - this._properties = null; - this._buffers = null; -}; + /** + * Destroys the container + * + */ + destroy() { + super.destroy(arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; + } + +} + +module.exports = ParticleContainer; diff --git a/src/particles/webgl/ParticleBuffer.js b/src/particles/webgl/ParticleBuffer.js index c9c7fd5..d3ea376 100644 --- a/src/particles/webgl/ParticleBuffer.js +++ b/src/particles/webgl/ParticleBuffer.js @@ -19,211 +19,213 @@ * @private * @memberof PIXI */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) +class ParticleBuffer { + constructor(gl, properties, dynamicPropertyFlags, size) { - var property = properties[i]; + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - // Make copy of properties object so that when we edit the offset it doesn't - // change all other instances of the object literal - property = - { - attribute:property.attribute, - size:property.size, - uploadFunction:property.uploadFunction, - offset:property.offset - }; + /** + * Size of a single vertex. + * + * @member {number} + */ + this.vertSize = 2; - if(dynamicPropertyFlags[i]) + /** + * Size of a single vertex in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of particles the buffer can hold + * + * @member {number} + */ + this.size = size; + + /** + * A list of the properties that are dynamic. + * + * @member {object[]} + */ + this.dynamicProperties = []; + + /** + * A list of the properties that are static. + * + * @member {object[]} + */ + this.staticProperties = []; + + for (var i = 0; i < properties.length; i++) { - this.dynamicProperties.push(property); + var property = properties[i]; + + // Make copy of properties object so that when we edit the offset it doesn't + // change all other instances of the object literal + property = + { + attribute:property.attribute, + size:property.size, + uploadFunction:property.uploadFunction, + offset:property.offset + }; + + if(dynamicPropertyFlags[i]) + { + this.dynamicProperties.push(property); + } + else + { + this.staticProperties.push(property); + } } - else + + this.staticStride = 0; + this.staticBuffer = null; + this.staticData = null; + + this.dynamicStride = 0; + this.dynamicBuffer = null; + this.dynamicData = null; + + this.initBuffers(); + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + initBuffers() + { + var gl = this.gl; + var i; + var property; + + var dynamicOffset = 0; + + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + + this.dynamicStride = 0; + + for (i = 0; i < this.dynamicProperties.length; i++) { - this.staticProperties.push(property); + property = this.dynamicProperties[i]; + + property.offset = dynamicOffset; + dynamicOffset += property.size; + this.dynamicStride += property.size; + } + + this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); + this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); + + // static // + var staticOffset = 0; + this.staticStride = 0; + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + + property.offset = staticOffset; + staticOffset += property.size; + this.staticStride += property.size; + + + } + + this.staticData = new Float32Array( this.size * this.staticStride * 4); + this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); + + + this.vao = new glCore.VertexArrayObject(gl) + .addIndex(this.indexBuffer); + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); + } + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); } } - this.staticStride = 0; - this.staticBuffer = null; - this.staticData = null; + /** + * Uploads the dynamic properties. + * + */ + uploadDynamic(children, startIndex, amount) + { + for (var i = 0; i < this.dynamicProperties.length; i++) + { + var property = this.dynamicProperties[i]; + property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); + } - this.dynamicStride = 0; - this.dynamicBuffer = null; - this.dynamicData = null; + this.dynamicBuffer.upload(); + } - this.initBuffers(); + /** + * Uploads the static properties. + * + */ + uploadStatic(children, startIndex, amount) + { + for (var i = 0; i < this.staticProperties.length; i++) + { + var property = this.staticProperties[i]; + property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); + } + + this.staticBuffer.upload(); + } + + /** + * Binds the buffers to the GPU + * + */ + bind() + { + this.vao.bind(); + } + + /** + * Destroys the ParticleBuffer. + * + */ + destroy() + { + this.dynamicProperties = null; + this.dynamicData = null; + this.dynamicBuffer.destroy(); + + this.staticProperties = null; + this.staticData = null; + this.staticBuffer.destroy(); + } } -ParticleBuffer.prototype.constructor = ParticleBuffer; module.exports = ParticleBuffer; - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 6fb921c..5f0281a 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -20,411 +20,412 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function ParticleRenderer(renderer) -{ - core.ObjectRenderer.call(this, renderer); +class ParticleRenderer extends core.ObjectRenderer { + constructor(renderer) + { + super(renderer); - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - // var numIndices = 98304; + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + // and max number of element in the index buffer is 16384 * 6 = 98304 + // Creating a full index buffer, overhead is 98304 * 2 = 196Ko + // var numIndices = 98304; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + this.indexBuffer = null; + + this.properties = null; + + this.tempMatrix = new core.Matrix(); + + this.CONTEXT_UID = 0; + } /** - * The default shader that is used if a sprite doesn't have a more specific one. + * When there is a WebGL context change * - * @member {PIXI.Shader} + * @private */ - this.shader = null; + onContextChange() + { + var gl = this.renderer.gl; - this.indexBuffer = null; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.properties = null; + // setup default shader + this.shader = new ParticleShader(gl); - this.tempMatrix = new core.Matrix(); + this.properties = [ + // verticesData + { + attribute:this.shader.attributes.aVertexPosition, + size:2, + uploadFunction:this.uploadVertices, + offset:0 + }, + // positionData + { + attribute:this.shader.attributes.aPositionCoord, + size:2, + uploadFunction:this.uploadPosition, + offset:0 + }, + // rotationData + { + attribute:this.shader.attributes.aRotation, + size:1, + uploadFunction:this.uploadRotation, + offset:0 + }, + // uvsData + { + attribute:this.shader.attributes.aTextureCoord, + size:2, + uploadFunction:this.uploadUvs, + offset:0 + }, + // alphaData + { + attribute:this.shader.attributes.aColor, + size:1, + uploadFunction:this.uploadAlpha, + offset:0 + } + ]; - this.CONTEXT_UID = 0; + } + + /** + * Starts a new particle batch. + * + */ + start() + { + this.renderer.bindShader(this.shader); + } + + + /** + * Renders the particle container object. + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ + render(container) + { + var children = container.children, + totalChildren = children.length, + maxSize = container._maxSize, + batchSize = container._batchSize; + + if(totalChildren === 0) + { + return; + } + else if(totalChildren > maxSize) + { + totalChildren = maxSize; + } + + var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; + + if(!buffers) + { + buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); + } + + // if the uvs have not updated then no point rendering just yet! + this.renderer.setBlendMode(container.blendMode); + + var gl = this.renderer.gl; + + var m = container.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer._activeRenderTarget.projectionMatrix ); + this.shader.uniforms.projectionMatrix = m.toArray(true); + this.shader.uniforms.uAlpha = container.worldAlpha; + + + // make sure the texture is bound.. + var baseTexture = children[0]._texture.baseTexture; + + this.renderer.bindTexture(baseTexture); + + // now lets upload and render the buffers.. + for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) + { + var amount = ( totalChildren - i); + if(amount > batchSize) + { + amount = batchSize; + } + + var buffer = buffers[j]; + + // we always upload the dynamic + buffer.uploadDynamic(children, i, amount); + + // we only upload the static content when we have to! + if(container._bufferToUpdate === j) + { + buffer.uploadStatic(children, i, amount); + container._bufferToUpdate = j + 1; + } + + // bind the buffer + buffer.vao.bind() + .draw(gl.TRIANGLES, amount * 6) + .unbind(); + + // now draw those suckas! + // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); + // this.renderer.drawCount++; + } + } + + /** + * Creates one particle buffer for each child in the container we want to render and updates internal properties + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ + generateBuffers(container) + { + var gl = this.renderer.gl, + buffers = [], + size = container._maxSize, + batchSize = container._batchSize, + dynamicPropertyFlags = container._properties, + i; + + for (i = 0; i < size; i += batchSize) + { + buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); + } + + return buffers; + } + + /** + * Uploads the verticies. + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their vertices uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadVertices(children, startIndex, amount, array, stride, offset) + { + var sprite, + texture, + trim, + orig, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + trim = texture.trim; + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - sprite.anchor.y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = (orig.width ) * (1-sprite.anchor.x); + w1 = (orig.width ) * -sprite.anchor.x; + + h0 = orig.height * (1-sprite.anchor.y); + h1 = orig.height * -sprite.anchor.y; + } + + array[offset] = w1 * sx; + array[offset + 1] = h1 * sy; + + array[offset + stride] = w0 * sx; + array[offset + stride + 1] = h1 * sy; + + array[offset + stride * 2] = w0 * sx; + array[offset + stride * 2 + 1] = h0 * sy; + + array[offset + stride * 3] = w1 * sx; + array[offset + stride * 3 + 1] = h0 * sy; + + offset += stride * 4; + } + + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their positions uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadPosition(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spritePosition = children[startIndex + i].position; + + array[offset] = spritePosition.x; + array[offset + 1] = spritePosition.y; + + array[offset + stride] = spritePosition.x; + array[offset + stride + 1] = spritePosition.y; + + array[offset + stride * 2] = spritePosition.x; + array[offset + stride * 2 + 1] = spritePosition.y; + + array[offset + stride * 3] = spritePosition.x; + array[offset + stride * 3 + 1] = spritePosition.y; + + offset += stride * 4; + } + + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their rotation uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadRotation(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spriteRotation = children[startIndex + i].rotation; + + + array[offset] = spriteRotation; + array[offset + stride] = spriteRotation; + array[offset + stride * 2] = spriteRotation; + array[offset + stride * 3] = spriteRotation; + + offset += stride * 4; + } + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their Uvs uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadUvs(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var textureUvs = children[startIndex + i]._texture._uvs; + + if (textureUvs) + { + array[offset] = textureUvs.x0; + array[offset + 1] = textureUvs.y0; + + array[offset + stride] = textureUvs.x1; + array[offset + stride + 1] = textureUvs.y1; + + array[offset + stride * 2] = textureUvs.x2; + array[offset + stride * 2 + 1] = textureUvs.y2; + + array[offset + stride * 3] = textureUvs.x3; + array[offset + stride * 3 + 1] = textureUvs.y3; + + offset += stride * 4; + } + else + { + //TODO you know this can be easier! + array[offset] = 0; + array[offset + 1] = 0; + + array[offset + stride] = 0; + array[offset + stride + 1] = 0; + + array[offset + stride * 2] = 0; + array[offset + stride * 2 + 1] = 0; + + array[offset + stride * 3] = 0; + array[offset + stride * 3 + 1] = 0; + + offset += stride * 4; + } + } + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their alpha uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadAlpha(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spriteAlpha = children[startIndex + i].alpha; + + array[offset] = spriteAlpha; + array[offset + stride] = spriteAlpha; + array[offset + stride * 2] = spriteAlpha; + array[offset + stride * 3] = spriteAlpha; + + offset += stride * 4; + } + } + + + /** + * Destroys the ParticleRenderer. + * + */ + destroy() + { + if (this.renderer.gl) { + this.renderer.gl.deleteBuffer(this.indexBuffer); + } + core.ObjectRenderer.prototype.destroy.apply(this, arguments); + + this.shader.destroy(); + + this.indices = null; + this.tempMatrix = null; + } + } -ParticleRenderer.prototype = Object.create(core.ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; module.exports = ParticleRenderer; core.WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - var m = container.worldTransform.copy( this.tempMatrix ); - m.prepend( this.renderer._activeRenderTarget.projectionMatrix ); - this.shader.uniforms.projectionMatrix = m.toArray(true); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind() - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = function (children, startIndex, amount, array, stride, offset) -{ - var sprite, - texture, - trim, - orig, - sx, - sy, - w0, w1, h0, h1; - - for (var i = 0; i < amount; i++) { - - sprite = children[startIndex + i]; - texture = sprite._texture; - sx = sprite.scale.x; - sy = sprite.scale.y; - trim = texture.trim; - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (orig.width ) * (1-sprite.anchor.x); - w1 = (orig.width ) * -sprite.anchor.x; - - h0 = orig.height * (1-sprite.anchor.y); - h1 = orig.height * -sprite.anchor.y; - } - - array[offset] = w1 * sx; - array[offset + 1] = h1 * sy; - - array[offset + stride] = w0 * sx; - array[offset + stride + 1] = h1 * sy; - - array[offset + stride * 2] = w0 * sx; - array[offset + stride * 2 + 1] = h0 * sy; - - array[offset + stride * 3] = w1 * sx; - array[offset + stride * 3 + 1] = h0 * sy; - - offset += stride * 4; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadPosition = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spritePosition = children[startIndex + i].position; - - array[offset] = spritePosition.x; - array[offset + 1] = spritePosition.y; - - array[offset + stride] = spritePosition.x; - array[offset + stride + 1] = spritePosition.y; - - array[offset + stride * 2] = spritePosition.x; - array[offset + stride * 2 + 1] = spritePosition.y; - - array[offset + stride * 3] = spritePosition.x; - array[offset + stride * 3 + 1] = spritePosition.y; - - offset += stride * 4; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadRotation = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spriteRotation = children[startIndex + i].rotation; - - - array[offset] = spriteRotation; - array[offset + stride] = spriteRotation; - array[offset + stride * 2] = spriteRotation; - array[offset + stride * 3] = spriteRotation; - - offset += stride * 4; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadUvs = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var textureUvs = children[startIndex + i]._texture._uvs; - - if (textureUvs) - { - array[offset] = textureUvs.x0; - array[offset + 1] = textureUvs.y0; - - array[offset + stride] = textureUvs.x1; - array[offset + stride + 1] = textureUvs.y1; - - array[offset + stride * 2] = textureUvs.x2; - array[offset + stride * 2 + 1] = textureUvs.y2; - - array[offset + stride * 3] = textureUvs.x3; - array[offset + stride * 3 + 1] = textureUvs.y3; - - offset += stride * 4; - } - else - { - //TODO you know this can be easier! - array[offset] = 0; - array[offset + 1] = 0; - - array[offset + stride] = 0; - array[offset + stride + 1] = 0; - - array[offset + stride * 2] = 0; - array[offset + stride * 2 + 1] = 0; - - array[offset + stride * 3] = 0; - array[offset + stride * 3 + 1] = 0; - - offset += stride * 4; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadAlpha = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spriteAlpha = children[startIndex + i].alpha; - - array[offset] = spriteAlpha; - array[offset + stride] = spriteAlpha; - array[offset + stride * 2] = spriteAlpha; - array[offset + stride * 3] = spriteAlpha; - - offset += stride * 4; - } -}; - - -/** - * Destroys the ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - core.ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 4c98aab..12eefb7 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -6,59 +6,58 @@ * @memberof PIXI * @param gl {PIXI.Shader} The webgl shader manager this shader works for. */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', +class ParticleShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', + 'attribute vec2 aPositionCoord;', + 'attribute vec2 aScale;', + 'attribute float aRotation;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', - 'varying float vColor;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', - 'void main(void){', - ' vec2 v = aVertexPosition;', + 'void main(void){', + ' vec2 v = aVertexPosition;', - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', + ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', + ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', + ' v = v + aPositionCoord;', - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', + ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'varying vec2 vTextureCoord;', - 'varying float vColor;', + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' + ].join('\n'), + // hello + [ + 'varying vec2 vTextureCoord;', + 'varying float vColor;', - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', + 'uniform sampler2D uSampler;', + 'uniform float uAlpha;', - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); + 'void main(void){', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' if (color.a == 0.0) discard;', + ' gl_FragColor = color;', + '}' + ].join('\n') + ); - // TEMP HACK + // TEMP HACK + } } -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - module.exports = ParticleShader; diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 89eb104..1bdf6c0 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -7,53 +7,55 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasPrepare() -{ +class CanvasPrepare { + constructor() + { + } + + /** + * Stub method for upload. + * @param {Function|PIXI.DisplayObject|PIXI.Container} item Either + * the container or display object to search for items to upload or + * the callback function, if items have been added using `prepare.add`. + * @param {Function} done When completed + */ + upload(displayObject, done) + { + if (typeof displayObject === 'function') + { + done = displayObject; + displayObject = null; + } + done(); + } + + /** + * Stub method for registering hooks. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + register() + { + return this; + } + + /** + * Stub method for adding items. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + add() + { + return this; + } + + /** + * Stub method for destroying plugin. + */ + destroy() + { + } + } -CanvasPrepare.prototype.constructor = CanvasPrepare; module.exports = CanvasPrepare; -/** - * Stub method for upload. - * @param {Function|PIXI.DisplayObject|PIXI.Container} item Either - * the container or display object to search for items to upload or - * the callback function, if items have been added using `prepare.add`. - * @param {Function} done When completed - */ -CanvasPrepare.prototype.upload = function(displayObject, done) -{ - if (typeof displayObject === 'function') - { - done = displayObject; - displayObject = null; - } - done(); -}; - -/** - * Stub method for registering hooks. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ -CanvasPrepare.prototype.register = function() -{ - return this; -}; - -/** - * Stub method for adding items. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ -CanvasPrepare.prototype.add = function() -{ - return this; -}; - -/** - * Stub method for destroying plugin. - */ -CanvasPrepare.prototype.destroy = function() -{ -}; - -core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); \ No newline at end of file +core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/.jshintrc b/.jshintrc index 79b981b..72e96c5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -34,6 +34,7 @@ "maxparams" : 8, // Prohibit having more than X number of params in a function. "maxdepth" : 8, // Prohibit nested blocks from going more than X levels deep. "maxlen" : 220, // Require that all lines are 100 characters or less. + "esversion" : 6, // ECMAScript 2015 syntax allowed "globals" : { // Register globals that are used in the code. "performance": false // Depends on polyfill, simplifies usage }, diff --git a/package.json b/package.json index 29f6a6d..60e808e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "browserify-versionify": "^1.0.6" }, "devDependencies": { + "babel-preset-es2015": "^6.14.0", + "babelify": "^7.3.0", "del": "^2.2.0", "electron-prebuilt": "^1.3.2", "floss": "^0.7.1", @@ -68,6 +70,14 @@ }, "browserify": { "transform": [ + [ + "babelify", + { + "presets": [ + "es2015" + ] + } + ], "glslify", "browserify-versionify" ] diff --git a/src/accessibility/AccessibilityManager.js b/src/accessibility/AccessibilityManager.js index 24d8ad6..35d66dc 100644 --- a/src/accessibility/AccessibilityManager.js +++ b/src/accessibility/AccessibilityManager.js @@ -7,7 +7,6 @@ require('./accessibleTarget') ); - /** * The Accessibility manager reacreates the ability to tab and and have content read by screen readers. This is very important as it can possibly help people with disabilities access pixi content. * Much like interaction any DisplayObject can be made accessible. This manager will map the events as if the mouse was being used, minimizing the efferot required to implement. @@ -16,440 +15,439 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer|PIXI.WebGLRenderer} A reference to the current renderer */ -function AccessibilityManager(renderer) -{ - if(Device.tablet || Device.phone) - { - this.createTouchHook(); - } +class AccessibilityManager { + constructor(renderer) + { + if(Device.tablet || Device.phone) + { + this.createTouchHook(); + } - // first we create a div that will sit over the pixi element. This is where the div overlays will go. - var div = document.createElement('div'); + // first we create a div that will sit over the pixi element. This is where the div overlays will go. + var div = document.createElement('div'); - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.position = 'absolute'; - div.style.top = 0; - div.style.left = 0; - // - div.style.zIndex = 2; + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.position = 'absolute'; + div.style.top = 0; + div.style.left = 0; + // + div.style.zIndex = 2; - /** - * This is the dom element that will sit over the pixi element. This is where the div overlays will go. - * - * @type {HTMLElement} - * @private - */ - this.div = div; + /** + * This is the dom element that will sit over the pixi element. This is where the div overlays will go. + * + * @type {HTMLElement} + * @private + */ + this.div = div; - /** - * A simple pool for storing divs. - * - * @type {*} - * @private - */ - this.pool = []; + /** + * A simple pool for storing divs. + * + * @type {*} + * @private + */ + this.pool = []; - /** - * This is a tick used to check if an object is no longer being rendered. - * - * @type {Number} - * @private - */ - this.renderId = 0; + /** + * This is a tick used to check if an object is no longer being rendered. + * + * @type {Number} + * @private + */ + this.renderId = 0; - /** - * Setting this to true will visually show the divs - * - * @type {boolean} - */ - this.debug = false; + /** + * Setting this to true will visually show the divs + * + * @type {boolean} + */ + this.debug = false; - /** - * The renderer this accessibility manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; + /** + * The renderer this accessibility manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; - /** - * The array of currently active accessible items. - * - * @member {Array<*>} + /** + * The array of currently active accessible items. + * + * @member {Array<*>} + * @private + */ + this.children = []; + + /** + * pre-bind the functions + * + * @private + */ + this._onKeyDown = this._onKeyDown.bind(this); + this._onMouseMove = this._onMouseMove.bind(this); + + /** + * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. + * + * @member {Array<*>} + * @private + */ + this.isActive = false; + this.isMobileAccessabillity = false; + + // let listen for tab.. once pressed we can fire up and show the accessibility layer + window.addEventListener('keydown', this._onKeyDown, false); + } + + createTouchHook() + { + var hookDiv = document.createElement('button'); + hookDiv.style.width = 1 + 'px'; + hookDiv.style.height = 1 + 'px'; + hookDiv.style.position = 'absolute'; + hookDiv.style.top = -1000+'px'; + hookDiv.style.left = -1000+'px'; + hookDiv.style.zIndex = 2; + hookDiv.style.backgroundColor = '#FF0000'; + hookDiv.title = 'HOOK DIV'; + + hookDiv.addEventListener('focus', function(){ + + this.isMobileAccessabillity = true; + this.activate(); + document.body.removeChild(hookDiv); + + }.bind(this)); + + document.body.appendChild(hookDiv); + + } + + /** + * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key * @private */ - this.children = []; + activate() + { + if(this.isActive ) + { + return; + } - /** - * pre-bind the functions - * - * @private - */ - this._onKeyDown = this._onKeyDown.bind(this); - this._onMouseMove = this._onMouseMove.bind(this); + this.isActive = true; - /** - * stores the state of the manager. If there are no accessible objects or the mouse is moving the will be false. - * - * @member {Array<*>} + window.document.addEventListener('mousemove', this._onMouseMove, true); + window.removeEventListener('keydown', this._onKeyDown, false); + + this.renderer.on('postrender', this.update, this); + + if(this.renderer.view.parentNode) + { + this.renderer.view.parentNode.appendChild(this.div); + } + } + + /** + * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse * @private */ - this.isActive = false; - this.isMobileAccessabillity = false; + deactivate() + { - // let listen for tab.. once pressed we can fire up and show the accessibility layer - window.addEventListener('keydown', this._onKeyDown, false); + if(!this.isActive || this.isMobileAccessabillity) + { + return; + } + + this.isActive = false; + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.addEventListener('keydown', this._onKeyDown, false); + + this.renderer.off('postrender', this.update); + + if(this.div.parentNode) + { + this.div.parentNode.removeChild(this.div); + } + + } + + /** + * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. + * @param displayObject {PIXI.Container} the DisplayObject to check. + * @private + */ + updateAccessibleObjects(displayObject) + { + if(!displayObject.visible) + { + return; + } + + if(displayObject.accessible && displayObject.interactive) + { + if(!displayObject._accessibleActive) + { + this.addChild(displayObject); + } + + displayObject.renderId = this.renderId; + } + + var children = displayObject.children; + + for (var i = children.length - 1; i >= 0; i--) { + + this.updateAccessibleObjects(children[i]); + } + } + + + /** + * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects + * @private + */ + update() + { + if(!this.renderer.renderingToScreen) { + return; + } + + // update children... + this.updateAccessibleObjects(this.renderer._lastObjectRendered); + + var rect = this.renderer.view.getBoundingClientRect(); + var sx = rect.width / this.renderer.width; + var sy = rect.height / this.renderer.height; + + var div = this.div; + + div.style.left = rect.left + 'px'; + div.style.top = rect.top + 'px'; + div.style.width = this.renderer.width + 'px'; + div.style.height = this.renderer.height + 'px'; + + for (var i = 0; i < this.children.length; i++) + { + + var child = this.children[i]; + + if(child.renderId !== this.renderId) + { + child._accessibleActive = false; + + core.utils.removeItems(this.children, i, 1); + this.div.removeChild( child._accessibleDiv ); + this.pool.push(child._accessibleDiv); + child._accessibleDiv = null; + + i--; + + if(this.children.length === 0) + { + this.deactivate(); + } + } + else + { + // map div to display.. + div = child._accessibleDiv; + var hitArea = child.hitArea; + var wt = child.worldTransform; + + if(child.hitArea) + { + div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; + div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; + + div.style.width = (hitArea.width * wt.a * sx) + 'px'; + div.style.height = (hitArea.height * wt.d * sy) + 'px'; + + } + else + { + hitArea = child.getBounds(); + + this.capHitArea(hitArea); + + div.style.left = (hitArea.x * sx) + 'px'; + div.style.top = (hitArea.y * sy) + 'px'; + + div.style.width = (hitArea.width * sx) + 'px'; + div.style.height = (hitArea.height * sy) + 'px'; + } + } + } + + // increment the render id.. + this.renderId++; + } + + capHitArea(hitArea) + { + if (hitArea.x < 0) + { + hitArea.width += hitArea.x; + hitArea.x = 0; + } + + if (hitArea.y < 0) + { + hitArea.height += hitArea.y; + hitArea.y = 0; + } + + if ( hitArea.x + hitArea.width > this.renderer.width ) + { + hitArea.width = this.renderer.width - hitArea.x; + } + + if ( hitArea.y + hitArea.height > this.renderer.height ) + { + hitArea.height = this.renderer.height - hitArea.y; + } + } + + /** + * Adds a DisplayObject to the accessibility manager + * @private + */ + addChild(displayObject) + { + // this.activate(); + + var div = this.pool.pop(); + + if(!div) + { + div = document.createElement('button'); + + div.style.width = 100 + 'px'; + div.style.height = 100 + 'px'; + div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; + div.style.position = 'absolute'; + div.style.zIndex = 2; + div.style.borderStyle = 'none'; + + + div.addEventListener('click', this._onClick.bind(this)); + div.addEventListener('focus', this._onFocus.bind(this)); + div.addEventListener('focusout', this._onFocusOut.bind(this)); + } + + + if(displayObject.accessibleTitle) + { + div.title = displayObject.accessibleTitle; + } + else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) + { + div.title = 'displayObject ' + this.tabIndex; + } + + if(displayObject.accessibleHint) + { + div.setAttribute('aria-label', displayObject.accessibleHint); + } + + + // + + displayObject._accessibleActive = true; + displayObject._accessibleDiv = div; + div.displayObject = displayObject; + + + this.children.push(displayObject); + this.div.appendChild( displayObject._accessibleDiv ); + displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; + } + + + /** + * Maps the div button press to pixi's InteractionManager (click) + * @private + */ + _onClick(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseover) + * @private + */ + _onFocus(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); + } + + /** + * Maps the div focus events to pixis InteractionManager (mouseout) + * @private + */ + _onFocusOut(e) + { + var interactionManager = this.renderer.plugins.interaction; + interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); + } + + /** + * Is called when a key is pressed + * + * @private + */ + _onKeyDown(e) + { + if(e.keyCode !== 9) + { + return; + } + + this.activate(); + } + + /** + * Is called when the mouse moves across the renderer element + * + * @private + */ + _onMouseMove() + { + this.deactivate(); + } + + + /** + * Destroys the accessibility manager + * + */ + destroy() + { + this.div = null; + + for (var i = 0; i < this.children.length; i++) + { + this.children[i].div = null; + } + + + window.document.removeEventListener('mousemove', this._onMouseMove); + window.removeEventListener('keydown', this._onKeyDown); + + this.pool = null; + this.children = null; + this.renderer = null; + + } } - -AccessibilityManager.prototype.constructor = AccessibilityManager; module.exports = AccessibilityManager; -AccessibilityManager.prototype.createTouchHook = function() -{ - var hookDiv = document.createElement('button'); - hookDiv.style.width = 1 + 'px'; - hookDiv.style.height = 1 + 'px'; - hookDiv.style.position = 'absolute'; - hookDiv.style.top = -1000+'px'; - hookDiv.style.left = -1000+'px'; - hookDiv.style.zIndex = 2; - hookDiv.style.backgroundColor = '#FF0000'; - hookDiv.title = 'HOOK DIV'; - - hookDiv.addEventListener('focus', function(){ - - this.isMobileAccessabillity = true; - this.activate(); - document.body.removeChild(hookDiv); - - }.bind(this)); - - document.body.appendChild(hookDiv); - -}; - -/** - * Activating will cause the Accessibility layer to be shown. This is called when a user preses the tab key - * @private - */ -AccessibilityManager.prototype.activate = function() -{ - if(this.isActive ) - { - return; - } - - this.isActive = true; - - window.document.addEventListener('mousemove', this._onMouseMove, true); - window.removeEventListener('keydown', this._onKeyDown, false); - - this.renderer.on('postrender', this.update, this); - - if(this.renderer.view.parentNode) - { - this.renderer.view.parentNode.appendChild(this.div); - } -}; - -/** - * Deactivating will cause the Accessibility layer to be hidden. This is called when a user moves the mouse - * @private - */ -AccessibilityManager.prototype.deactivate = function() -{ - - if(!this.isActive || this.isMobileAccessabillity) - { - return; - } - - this.isActive = false; - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.addEventListener('keydown', this._onKeyDown, false); - - this.renderer.off('postrender', this.update); - - if(this.div.parentNode) - { - this.div.parentNode.removeChild(this.div); - } - -}; - -/** - * This recursive function will run throught he scene graph and add any new accessible objects to the DOM layer. - * @param displayObject {PIXI.Container} the DisplayObject to check. - * @private - */ -AccessibilityManager.prototype.updateAccessibleObjects = function(displayObject) -{ - if(!displayObject.visible) - { - return; - } - - if(displayObject.accessible && displayObject.interactive) - { - if(!displayObject._accessibleActive) - { - this.addChild(displayObject); - } - - displayObject.renderId = this.renderId; - } - - var children = displayObject.children; - - for (var i = children.length - 1; i >= 0; i--) { - - this.updateAccessibleObjects(children[i]); - } -}; - - -/** - * Before each render this function will ensure that all divs are mapped correctly to their DisplayObjects - * @private - */ -AccessibilityManager.prototype.update = function() -{ - if(!this.renderer.renderingToScreen) { - return; - } - - // update children... - this.updateAccessibleObjects(this.renderer._lastObjectRendered); - - var rect = this.renderer.view.getBoundingClientRect(); - var sx = rect.width / this.renderer.width; - var sy = rect.height / this.renderer.height; - - var div = this.div; - - div.style.left = rect.left + 'px'; - div.style.top = rect.top + 'px'; - div.style.width = this.renderer.width + 'px'; - div.style.height = this.renderer.height + 'px'; - - for (var i = 0; i < this.children.length; i++) - { - - var child = this.children[i]; - - if(child.renderId !== this.renderId) - { - child._accessibleActive = false; - - core.utils.removeItems(this.children, i, 1); - this.div.removeChild( child._accessibleDiv ); - this.pool.push(child._accessibleDiv); - child._accessibleDiv = null; - - i--; - - if(this.children.length === 0) - { - this.deactivate(); - } - } - else - { - // map div to display.. - div = child._accessibleDiv; - var hitArea = child.hitArea; - var wt = child.worldTransform; - - if(child.hitArea) - { - div.style.left = ((wt.tx + (hitArea.x * wt.a)) * sx) + 'px'; - div.style.top = ((wt.ty + (hitArea.y * wt.d)) * sy) + 'px'; - - div.style.width = (hitArea.width * wt.a * sx) + 'px'; - div.style.height = (hitArea.height * wt.d * sy) + 'px'; - - } - else - { - hitArea = child.getBounds(); - - this.capHitArea(hitArea); - - div.style.left = (hitArea.x * sx) + 'px'; - div.style.top = (hitArea.y * sy) + 'px'; - - div.style.width = (hitArea.width * sx) + 'px'; - div.style.height = (hitArea.height * sy) + 'px'; - } - } - } - - // increment the render id.. - this.renderId++; -}; - -AccessibilityManager.prototype.capHitArea = function (hitArea) -{ - if (hitArea.x < 0) - { - hitArea.width += hitArea.x; - hitArea.x = 0; - } - - if (hitArea.y < 0) - { - hitArea.height += hitArea.y; - hitArea.y = 0; - } - - if ( hitArea.x + hitArea.width > this.renderer.width ) - { - hitArea.width = this.renderer.width - hitArea.x; - } - - if ( hitArea.y + hitArea.height > this.renderer.height ) - { - hitArea.height = this.renderer.height - hitArea.y; - } -}; - - -/** - * Adds a DisplayObject to the accessibility manager - * @private - */ -AccessibilityManager.prototype.addChild = function(displayObject) -{ -// this.activate(); - - var div = this.pool.pop(); - - if(!div) - { - div = document.createElement('button'); - - div.style.width = 100 + 'px'; - div.style.height = 100 + 'px'; - div.style.backgroundColor = this.debug ? 'rgba(255,0,0,0.5)' : 'transparent'; - div.style.position = 'absolute'; - div.style.zIndex = 2; - div.style.borderStyle = 'none'; - - - div.addEventListener('click', this._onClick.bind(this)); - div.addEventListener('focus', this._onFocus.bind(this)); - div.addEventListener('focusout', this._onFocusOut.bind(this)); - } - - - if(displayObject.accessibleTitle) - { - div.title = displayObject.accessibleTitle; - } - else if (!displayObject.accessibleTitle && !displayObject.accessibleHint) - { - div.title = 'displayObject ' + this.tabIndex; - } - - if(displayObject.accessibleHint) - { - div.setAttribute('aria-label', displayObject.accessibleHint); - } - - - // - - displayObject._accessibleActive = true; - displayObject._accessibleDiv = div; - div.displayObject = displayObject; - - - this.children.push(displayObject); - this.div.appendChild( displayObject._accessibleDiv ); - displayObject._accessibleDiv.tabIndex = displayObject.tabIndex; -}; - - -/** - * Maps the div button press to pixi's InteractionManager (click) - * @private - */ -AccessibilityManager.prototype._onClick = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'click', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseover) - * @private - */ -AccessibilityManager.prototype._onFocus = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseover', interactionManager.eventData); -}; - -/** - * Maps the div focus events to pixis InteractionManager (mouseout) - * @private - */ -AccessibilityManager.prototype._onFocusOut = function(e) -{ - var interactionManager = this.renderer.plugins.interaction; - interactionManager.dispatchEvent(e.target.displayObject, 'mouseout', interactionManager.eventData); -}; - -/** - * Is called when a key is pressed - * - * @private - */ -AccessibilityManager.prototype._onKeyDown = function(e) -{ - if(e.keyCode !== 9) - { - return; - } - - this.activate(); -}; - -/** - * Is called when the mouse moves across the renderer element - * - * @private - */ -AccessibilityManager.prototype._onMouseMove = function() -{ - this.deactivate(); -}; - - -/** - * Destroys the accessibility manager - * - */ -AccessibilityManager.prototype.destroy = function () -{ - this.div = null; - - for (var i = 0; i < this.children.length; i++) - { - this.children[i].div = null; - } - - - window.document.removeEventListener('mousemove', this._onMouseMove); - window.removeEventListener('keydown', this._onKeyDown); - - this.pool = null; - this.children = null; - this.renderer = null; - -}; - core.WebGLRenderer.registerPlugin('accessibility', AccessibilityManager); core.CanvasRenderer.registerPlugin('accessibility', AccessibilityManager); diff --git a/src/core/Shader.js b/src/core/Shader.js index 1fa984d..5a0800a 100644 --- a/src/core/Shader.js +++ b/src/core/Shader.js @@ -26,10 +26,10 @@ * @param vertexSrc {string|string[]} The vertex shader source as an array of strings. * @param fragmentSrc {string|string[]} The fragment shader source as an array of strings. */ -var Shader = function(gl, vertexSrc, fragmentSrc) { - GLShader.call(this, gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); -}; +class Shader extends GLShader { + constructor(gl, vertexSrc, fragmentSrc) { + super(gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); + } +} -Shader.prototype = Object.create(GLShader.prototype); -Shader.prototype.constructor = Shader; module.exports = Shader; diff --git a/src/core/display/Bounds.js b/src/core/display/Bounds.js index 3f40416..f9235aa 100644 --- a/src/core/display/Bounds.js +++ b/src/core/display/Bounds.js @@ -9,215 +9,216 @@ * @class * @memberof PIXI */ -function Bounds() -{ - /** - * @member {number} - * @default 0 - */ - this.minX = Infinity; +class Bounds { + constructor() + { + /** + * @member {number} + * @default 0 + */ + this.minX = Infinity; - /** - * @member {number} - * @default 0 - */ - this.minY = Infinity; + /** + * @member {number} + * @default 0 + */ + this.minY = Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxX = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxX = -Infinity; - /** - * @member {number} - * @default 0 - */ - this.maxY = -Infinity; + /** + * @member {number} + * @default 0 + */ + this.maxY = -Infinity; - this.rect = null; -} - -Bounds.prototype.constructor = Bounds; -module.exports = Bounds; - -Bounds.prototype.isEmpty = function() -{ - return this.minX > this.maxX || this.minY > this.maxY; -}; - -Bounds.prototype.clear = function() -{ - this.updateID++; - - this.minX = Infinity; - this.minY = Infinity; - this.maxX = -Infinity; - this.maxY = -Infinity; -}; - -/** - * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle - * It is not guaranteed that it will return tempRect - * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty - * @returns {PIXI.Rectangle} - */ -Bounds.prototype.getRectangle = function(rect) -{ - if (this.minX > this.maxX || this.minY > this.maxY) { - return Rectangle.EMPTY; + this.rect = null; } - rect = rect || new Rectangle(0, 0, 1, 1); - - rect.x = this.minX; - rect.y = this.minY; - rect.width = this.maxX - this.minX; - rect.height = this.maxY - this.minY; - - return rect; -}; - -/** - * This function should be inlined when its possible - * @param point {PIXI.Point} - */ -Bounds.prototype.addPoint = function (point) -{ - this.minX = Math.min(this.minX, point.x); - this.maxX = Math.max(this.maxX, point.x); - this.minY = Math.min(this.minY, point.y); - this.maxY = Math.max(this.maxY, point.y); -}; - -/** - * Adds a quad, not transformed - * @param vertices {Float32Array} - * @returns {PIXI.Bounds} - */ -Bounds.prototype.addQuad = function(vertices) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = vertices[0]; - var y = vertices[1]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[2]; - y = vertices[3]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[4]; - y = vertices[5]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = vertices[6]; - y = vertices[7]; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * Adds sprite frame, transformed - * @param transform {PIXI.TransformBase} - * @param x0 {number} - * @param y0 {number} - * @param x1 {number} - * @param y1 {number} - */ -Bounds.prototype.addFrame = function(transform, x0, y0, x1, y1) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - var x = a * x0 + c * y0 + tx; - var y = b * x0 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y0 + tx; - y = b * x1 + d * y0 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x0 + c * y1 + tx; - y = b * x0 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - x = a * x1 + c * y1 + tx; - y = b * x1 + d * y1 + ty; - minX = x < minX ? x : minX; - minY = y < minY ? y : minY; - maxX = x > maxX ? x : maxX; - maxY = y > maxY ? y : maxY; - - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; - -/** - * add an array of vertices - * @param transform {PIXI.TransformBase} - * @param vertices {Float32Array} - * @param beginOffset {number} - * @param endOffset {number} - */ -Bounds.prototype.addVertices = function(transform, vertices, beginOffset, endOffset) -{ - var matrix = transform.worldTransform; - var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; - - for (var i = beginOffset; i < endOffset; i += 2) + isEmpty() { - var rawX = vertices[i], rawY = vertices[i + 1]; - var x = (a * rawX) + (c * rawY) + tx; - var y = (d * rawY) + (b * rawX) + ty; + return this.minX > this.maxX || this.minY > this.maxY; + } + clear() + { + this.updateID++; + + this.minX = Infinity; + this.minY = Infinity; + this.maxX = -Infinity; + this.maxY = -Infinity; + } + + /** + * Can return Rectangle.EMPTY constant, either construct new rectangle, either use your rectangle + * It is not guaranteed that it will return tempRect + * @param tempRect {PIXI.Rectangle} temporary object will be used if AABB is not empty + * @returns {PIXI.Rectangle} + */ + getRectangle(rect) + { + if (this.minX > this.maxX || this.minY > this.maxY) { + return Rectangle.EMPTY; + } + + rect = rect || new Rectangle(0, 0, 1, 1); + + rect.x = this.minX; + rect.y = this.minY; + rect.width = this.maxX - this.minX; + rect.height = this.maxY - this.minY; + + return rect; + } + + /** + * This function should be inlined when its possible + * @param point {PIXI.Point} + */ + addPoint(point) + { + this.minX = Math.min(this.minX, point.x); + this.maxX = Math.max(this.maxX, point.x); + this.minY = Math.min(this.minY, point.y); + this.maxY = Math.max(this.maxY, point.y); + } + + /** + * Adds a quad, not transformed + * @param vertices {Float32Array} + * @returns {PIXI.Bounds} + */ + addQuad(vertices) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + var x = vertices[0]; + var y = vertices[1]; minX = x < minX ? x : minX; minY = y < minY ? y : minY; maxX = x > maxX ? x : maxX; maxY = y > maxY ? y : maxY; + + x = vertices[2]; + y = vertices[3]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[4]; + y = vertices[5]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = vertices[6]; + y = vertices[7]; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; } - this.minX = minX; - this.minY = minY; - this.maxX = maxX; - this.maxY = maxY; -}; + /** + * Adds sprite frame, transformed + * @param transform {PIXI.TransformBase} + * @param x0 {number} + * @param y0 {number} + * @param x1 {number} + * @param y1 {number} + */ + addFrame(transform, x0, y0, x1, y1) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; -Bounds.prototype.addBounds = function(bounds) -{ - var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + var x = a * x0 + c * y0 + tx; + var y = b * x0 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; - this.minX = bounds.minX < minX ? bounds.minX : minX; - this.minY = bounds.minY < minY ? bounds.minY : minY; - this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; - this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; -}; + x = a * x1 + c * y0 + tx; + y = b * x1 + d * y0 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x0 + c * y1 + tx; + y = b * x0 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + x = a * x1 + c * y1 + tx; + y = b * x1 + d * y1 + ty; + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + /** + * add an array of vertices + * @param transform {PIXI.TransformBase} + * @param vertices {Float32Array} + * @param beginOffset {number} + * @param endOffset {number} + */ + addVertices(transform, vertices, beginOffset, endOffset) + { + var matrix = transform.worldTransform; + var a = matrix.a, b = matrix.b, c = matrix.c, d = matrix.d, tx = matrix.tx, ty = matrix.ty; + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + for (var i = beginOffset; i < endOffset; i += 2) + { + var rawX = vertices[i], rawY = vertices[i + 1]; + var x = (a * rawX) + (c * rawY) + tx; + var y = (d * rawY) + (b * rawX) + ty; + + minX = x < minX ? x : minX; + minY = y < minY ? y : minY; + maxX = x > maxX ? x : maxX; + maxY = y > maxY ? y : maxY; + } + + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + } + + addBounds(bounds) + { + var minX = this.minX, minY = this.minY, maxX = this.maxX, maxY = this.maxY; + + this.minX = bounds.minX < minX ? bounds.minX : minX; + this.minY = bounds.minY < minY ? bounds.minY : minY; + this.maxX = bounds.maxX > maxX ? bounds.maxX : maxX; + this.maxY = bounds.maxY > maxY ? bounds.maxY : maxY; + } +} + +module.exports = Bounds; diff --git a/src/core/display/Container.js b/src/core/display/Container.js index 0352095..31ba184 100644 --- a/src/core/display/Container.js +++ b/src/core/display/Container.js @@ -13,22 +13,514 @@ * @extends PIXI.DisplayObject * @memberof PIXI */ -function Container() -{ - DisplayObject.call(this); +class Container extends DisplayObject { + constructor() + { + super(); + + /** + * The array of children of this container. + * + * @member {PIXI.DisplayObject[]} + * @readonly + */ + this.children = []; + } /** - * The array of children of this container. + * Overridable method that can be used by Container subclasses whenever the children array is modified * - * @member {PIXI.DisplayObject[]} - * @readonly + * @private */ - this.children = []; + onChildrenChange() {} + + /** + * Adds a child or multiple children to the container. + * + * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` + * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container + * @return {PIXI.DisplayObject} The first child that was added. + */ + addChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.addChild( arguments[i] ); + } + } + else + { + // if the child has a parent then lets remove it as Pixi objects can only exist in one place + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + // ensure a transform will be recalculated.. + this.transform._parentID = -1; + + this.children.push(child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(this.children.length-1); + child.emit('added', this); + } + + return child; + } + + /** + * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown + * + * @param child {PIXI.DisplayObject} The child to add + * @param index {number} The index to place the child in + * @return {PIXI.DisplayObject} The child that was added. + */ + addChildAt(child, index) + { + if (index >= 0 && index <= this.children.length) + { + if (child.parent) + { + child.parent.removeChild(child); + } + + child.parent = this; + + this.children.splice(index, 0, child); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('added', this); + + return child; + } + else + { + throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); + } + } + + /** + * Swaps the position of 2 Display Objects within this container. + * + * @param child {PIXI.DisplayObject} First display object to swap + * @param child2 {PIXI.DisplayObject} Second display object to swap + */ + swapChildren(child, child2) + { + if (child === child2) + { + return; + } + + var index1 = this.getChildIndex(child); + var index2 = this.getChildIndex(child2); + + if (index1 < 0 || index2 < 0) + { + throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); + } + + this.children[index1] = child2; + this.children[index2] = child; + this.onChildrenChange(index1 < index2 ? index1 : index2); + } + + /** + * Returns the index position of a child DisplayObject instance + * + * @param child {PIXI.DisplayObject} The DisplayObject instance to identify + * @return {number} The index position of the child display object to identify + */ + getChildIndex(child) + { + var index = this.children.indexOf(child); + + if (index === -1) + { + throw new Error('The supplied DisplayObject must be a child of the caller'); + } + + return index; + } + + /** + * Changes the position of an existing child in the display object container + * + * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number + * @param index {number} The resulting index number for the child display object + */ + setChildIndex(child, index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('The supplied index is out of bounds'); + } + + var currentIndex = this.getChildIndex(child); + + utils.removeItems(this.children, currentIndex, 1); // remove from old position + this.children.splice(index, 0, child); //add at new position + this.onChildrenChange(index); + } + + /** + * Returns the child at the specified index + * + * @param index {number} The index to get the child at + * @return {PIXI.DisplayObject} The child at the given index, if any. + */ + getChildAt(index) + { + if (index < 0 || index >= this.children.length) + { + throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); + } + + return this.children[index]; + } + + /** + * Removes a child from the container. + * + * @param child {PIXI.DisplayObject} The DisplayObject to remove + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChild(child) + { + var argumentsLength = arguments.length; + + // if there is only one argument we can bypass looping through the them + if(argumentsLength > 1) + { + // loop through the arguments property and add all children + // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes + for (var i = 0; i < argumentsLength; i++) + { + this.removeChild( arguments[i] ); + } + } + else + { + var index = this.children.indexOf(child); + + if (index === -1) + { + return; + } + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + } + + return child; + } + + /** + * Removes a child from the specified index position. + * + * @param index {number} The index to get the child from + * @return {PIXI.DisplayObject} The child that was removed. + */ + removeChildAt(index) + { + var child = this.getChildAt(index); + + child.parent = null; + utils.removeItems(this.children, index, 1); + + // TODO - lets either do all callbacks or all events.. not both! + this.onChildrenChange(index); + child.emit('removed', this); + + return child; + } + + /** + * Removes all children from this container that are within the begin and end indexes. + * + * @param [beginIndex=0] {number} The beginning position. + * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. + */ + removeChildren(beginIndex, endIndex) + { + var begin = beginIndex || 0; + var end = typeof endIndex === 'number' ? endIndex : this.children.length; + var range = end - begin; + var removed, i; + + if (range > 0 && range <= end) + { + removed = this.children.splice(begin, range); + + for (i = 0; i < removed.length; ++i) + { + removed[i].parent = null; + } + + this.onChildrenChange(beginIndex); + + for (i = 0; i < removed.length; ++i) + { + removed[i].emit('removed', this); + } + + return removed; + } + else if (range === 0 && this.children.length === 0) + { + return []; + } + else + { + throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); + } + } + + /* + * Updates the transform on all children of this container for rendering + * + * @private + */ + updateTransform() + { + this._boundsID++; + + if (!this.visible) + { + return; + } + + this.transform.updateTransform(this.parent.transform); + + //TODO: check render flags, how to process stuff here + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].updateTransform(); + } + } + + calculateBounds() + { + this._bounds.clear(); + + if(!this.visible) + { + return; + } + + this._calculateBounds(); + + for (var i = 0; i < this.children.length; i++) + { + var child = this.children[i]; + + child.calculateBounds(); + + this._bounds.addBounds(child._bounds); + } + + this._boundsID = this._lastBoundsID; + } + + _calculateBounds() + { + //FILL IN// + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + + // if the object is not visible or the alpha is 0 then no need to render this element + if (!this.visible || this.worldAlpha <= 0 || !this.renderable) + { + + return; + } + + + // do a quick check to see if this element has a mask or a filter. + if (this._mask || this._filters) + { + this.renderAdvancedWebGL(renderer); + } + else + { + this._renderWebGL(renderer); + + // simple render children! + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderWebGL(renderer); + } + } + } + + renderAdvancedWebGL(renderer) + { + renderer.currentRenderer.flush(); + + var filters = this._filters; + var mask = this._mask; + var i, j; + + // push filter first as we need to ensure the stencil buffer is correct for any masking + if ( filters ) + { + if(!this._enabledFilters) + { + this._enabledFilters = []; + } + + this._enabledFilters.length = 0; + + for (i = 0; i < filters.length; i++) + { + if(filters[i].enabled) + { + this._enabledFilters.push( filters[i] ); + } + } + + if( this._enabledFilters.length ) + { + renderer.filterManager.pushFilter(this, this._enabledFilters); + } + } + + if ( mask ) + { + renderer.maskManager.pushMask(this, this._mask); + } + + renderer.currentRenderer.start(); + + // add this object to the batch, only rendered if it has a texture. + this._renderWebGL(renderer); + + // now loop through the children and make sure they get rendered + for (i = 0, j = this.children.length; i < j; i++) + { + this.children[i].renderWebGL(renderer); + } + + renderer.currentRenderer.flush(); + + if ( mask ) + { + renderer.maskManager.popMask(this, this._mask); + } + + if ( filters && this._enabledFilters && this._enabledFilters.length ) + { + renderer.filterManager.popFilter(); + } + + renderer.currentRenderer.start(); + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.WebGLRenderer} The renderer + * @private + */ + _renderWebGL(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * To be overridden by the subclass + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) // jshint unused:false + { + // this is where content itself gets rendered... + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + */ + renderCanvas(renderer) + { + // if not visible or the alpha is 0 then no need to render this + if (!this.visible || this.alpha <= 0 || !this.renderable) + { + return; + } + + if (this._mask) + { + renderer.maskManager.pushMask(this._mask); + } + + this._renderCanvas(renderer); + for (var i = 0, j = this.children.length; i < j; ++i) + { + this.children[i].renderCanvas(renderer); + } + + if (this._mask) + { + renderer.maskManager.popMask(renderer); + } + } + + /** + * Removes all internal references and listeners as well as removes children from the display list. + * Do not use a Container after calling `destroy`. + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + */ + destroy(options) + { + super.destroy(); + + var destroyChildren = typeof options === 'boolean' ? options : options && options.children; + + var oldChildren = this.children; + this.children = null; + + if (destroyChildren) + { + for (var i = oldChildren.length - 1; i >= 0; i--) + { + var child = oldChildren[i]; + child.parent = null; + child.destroy(options); + } + } + } + } -// constructor -Container.prototype = Object.create(DisplayObject.prototype); -Container.prototype.constructor = Container; module.exports = Container; Object.defineProperties(Container.prototype, { @@ -92,499 +584,5 @@ } }); -/** - * Overridable method that can be used by Container subclasses whenever the children array is modified - * - * @private - */ -Container.prototype.onChildrenChange = function () {}; - -/** - * Adds a child or multiple children to the container. - * - * Multple items can be added like so: `myContainer.addChild(thinkOne, thingTwo, thingThree)` - * @param child {...PIXI.DisplayObject} The DisplayObject(s) to add to the container - * @return {PIXI.DisplayObject} The first child that was added. - */ -Container.prototype.addChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.addChild( arguments[i] ); - } - } - else - { - // if the child has a parent then lets remove it as Pixi objects can only exist in one place - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - // ensure a transform will be recalculated.. - this.transform._parentID = -1; - - this.children.push(child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(this.children.length-1); - child.emit('added', this); - } - - return child; -}; - -/** - * Adds a child to the container at a specified index. If the index is out of bounds an error will be thrown - * - * @param child {PIXI.DisplayObject} The child to add - * @param index {number} The index to place the child in - * @return {PIXI.DisplayObject} The child that was added. - */ -Container.prototype.addChildAt = function (child, index) -{ - if (index >= 0 && index <= this.children.length) - { - if (child.parent) - { - child.parent.removeChild(child); - } - - child.parent = this; - - this.children.splice(index, 0, child); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('added', this); - - return child; - } - else - { - throw new Error(child + 'addChildAt: The index '+ index +' supplied is out of bounds ' + this.children.length); - } -}; - -/** - * Swaps the position of 2 Display Objects within this container. - * - * @param child {PIXI.DisplayObject} First display object to swap - * @param child2 {PIXI.DisplayObject} Second display object to swap - */ -Container.prototype.swapChildren = function (child, child2) -{ - if (child === child2) - { - return; - } - - var index1 = this.getChildIndex(child); - var index2 = this.getChildIndex(child2); - - if (index1 < 0 || index2 < 0) - { - throw new Error('swapChildren: Both the supplied DisplayObjects must be children of the caller.'); - } - - this.children[index1] = child2; - this.children[index2] = child; - this.onChildrenChange(index1 < index2 ? index1 : index2); -}; - -/** - * Returns the index position of a child DisplayObject instance - * - * @param child {PIXI.DisplayObject} The DisplayObject instance to identify - * @return {number} The index position of the child display object to identify - */ -Container.prototype.getChildIndex = function (child) -{ - var index = this.children.indexOf(child); - - if (index === -1) - { - throw new Error('The supplied DisplayObject must be a child of the caller'); - } - - return index; -}; - -/** - * Changes the position of an existing child in the display object container - * - * @param child {PIXI.DisplayObject} The child DisplayObject instance for which you want to change the index number - * @param index {number} The resulting index number for the child display object - */ -Container.prototype.setChildIndex = function (child, index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('The supplied index is out of bounds'); - } - - var currentIndex = this.getChildIndex(child); - - utils.removeItems(this.children, currentIndex, 1); // remove from old position - this.children.splice(index, 0, child); //add at new position - this.onChildrenChange(index); -}; - -/** - * Returns the child at the specified index - * - * @param index {number} The index to get the child at - * @return {PIXI.DisplayObject} The child at the given index, if any. - */ -Container.prototype.getChildAt = function (index) -{ - if (index < 0 || index >= this.children.length) - { - throw new Error('getChildAt: Supplied index ' + index + ' does not exist in the child list, or the supplied DisplayObject is not a child of the caller'); - } - - return this.children[index]; -}; - -/** - * Removes a child from the container. - * - * @param child {PIXI.DisplayObject} The DisplayObject to remove - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChild = function (child) -{ - var argumentsLength = arguments.length; - - // if there is only one argument we can bypass looping through the them - if(argumentsLength > 1) - { - // loop through the arguments property and add all children - // use it the right way (.length and [i]) so that this function can still be optimised by JS runtimes - for (var i = 0; i < argumentsLength; i++) - { - this.removeChild( arguments[i] ); - } - } - else - { - var index = this.children.indexOf(child); - - if (index === -1) - { - return; - } - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - } - - return child; -}; - -/** - * Removes a child from the specified index position. - * - * @param index {number} The index to get the child from - * @return {PIXI.DisplayObject} The child that was removed. - */ -Container.prototype.removeChildAt = function (index) -{ - var child = this.getChildAt(index); - - child.parent = null; - utils.removeItems(this.children, index, 1); - - // TODO - lets either do all callbacks or all events.. not both! - this.onChildrenChange(index); - child.emit('removed', this); - - return child; -}; - -/** - * Removes all children from this container that are within the begin and end indexes. - * - * @param [beginIndex=0] {number} The beginning position. - * @param [endIndex=this.children.length] {number} The ending position. Default value is size of the container. - */ -Container.prototype.removeChildren = function (beginIndex, endIndex) -{ - var begin = beginIndex || 0; - var end = typeof endIndex === 'number' ? endIndex : this.children.length; - var range = end - begin; - var removed, i; - - if (range > 0 && range <= end) - { - removed = this.children.splice(begin, range); - - for (i = 0; i < removed.length; ++i) - { - removed[i].parent = null; - } - - this.onChildrenChange(beginIndex); - - for (i = 0; i < removed.length; ++i) - { - removed[i].emit('removed', this); - } - - return removed; - } - else if (range === 0 && this.children.length === 0) - { - return []; - } - else - { - throw new RangeError('removeChildren: numeric values are outside the acceptable range.'); - } -}; - -/* - * Updates the transform on all children of this container for rendering - * - * @private - */ -Container.prototype.updateTransform = function () -{ - this._boundsID++; - - if (!this.visible) - { - return; - } - - this.transform.updateTransform(this.parent.transform); - - //TODO: check render flags, how to process stuff here - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].updateTransform(); - } -}; - // performance increase to avoid using call.. (10x faster) Container.prototype.containerUpdateTransform = Container.prototype.updateTransform; - - -Container.prototype.calculateBounds = function () -{ - this._bounds.clear(); - - if(!this.visible) - { - return; - } - - this._calculateBounds(); - - for (var i = 0; i < this.children.length; i++) - { - var child = this.children[i]; - - child.calculateBounds(); - - this._bounds.addBounds(child._bounds); - } - - this._boundsID = this._lastBoundsID; -}; - -Container.prototype._calculateBounds = function () -{ - //FILL IN// -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Container.prototype.renderWebGL = function (renderer) -{ - - // if the object is not visible or the alpha is 0 then no need to render this element - if (!this.visible || this.worldAlpha <= 0 || !this.renderable) - { - - return; - } - - - // do a quick check to see if this element has a mask or a filter. - if (this._mask || this._filters) - { - this.renderAdvancedWebGL(renderer); - } - else - { - this._renderWebGL(renderer); - - // simple render children! - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderWebGL(renderer); - } - } -}; - -Container.prototype.renderAdvancedWebGL = function (renderer) -{ - renderer.currentRenderer.flush(); - - var filters = this._filters; - var mask = this._mask; - var i, j; - - // push filter first as we need to ensure the stencil buffer is correct for any masking - if ( filters ) - { - if(!this._enabledFilters) - { - this._enabledFilters = []; - } - - this._enabledFilters.length = 0; - - for (i = 0; i < filters.length; i++) - { - if(filters[i].enabled) - { - this._enabledFilters.push( filters[i] ); - } - } - - if( this._enabledFilters.length ) - { - renderer.filterManager.pushFilter(this, this._enabledFilters); - } - } - - if ( mask ) - { - renderer.maskManager.pushMask(this, this._mask); - } - - renderer.currentRenderer.start(); - - // add this object to the batch, only rendered if it has a texture. - this._renderWebGL(renderer); - - // now loop through the children and make sure they get rendered - for (i = 0, j = this.children.length; i < j; i++) - { - this.children[i].renderWebGL(renderer); - } - - renderer.currentRenderer.flush(); - - if ( mask ) - { - renderer.maskManager.popMask(this, this._mask); - } - - if ( filters && this._enabledFilters && this._enabledFilters.length ) - { - renderer.filterManager.popFilter(); - } - - renderer.currentRenderer.start(); -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -Container.prototype._renderWebGL = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - -/** - * To be overridden by the subclass - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Container.prototype._renderCanvas = function (renderer) // jshint unused:false -{ - // this is where content itself gets rendered... -}; - - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -Container.prototype.renderCanvas = function (renderer) -{ - // if not visible or the alpha is 0 then no need to render this - if (!this.visible || this.alpha <= 0 || !this.renderable) - { - return; - } - - if (this._mask) - { - renderer.maskManager.pushMask(this._mask); - } - - this._renderCanvas(renderer); - for (var i = 0, j = this.children.length; i < j; ++i) - { - this.children[i].renderCanvas(renderer); - } - - if (this._mask) - { - renderer.maskManager.popMask(renderer); - } -}; - -/** - * Removes all internal references and listeners as well as removes children from the display list. - * Do not use a Container after calling `destroy`. - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - */ -Container.prototype.destroy = function (options) -{ - DisplayObject.prototype.destroy.call(this); - - var destroyChildren = typeof options === 'boolean' ? options : options && options.children; - - var oldChildren = this.children; - this.children = null; - - if (destroyChildren) - { - for (var i = oldChildren.length - 1; i >= 0; i--) - { - var child = oldChildren[i]; - child.parent = null; - child.destroy(options); - } - } -}; diff --git a/src/core/display/DisplayObject.js b/src/core/display/DisplayObject.js index 6401c06..db2ce33 100644 --- a/src/core/display/DisplayObject.js +++ b/src/core/display/DisplayObject.js @@ -3,8 +3,8 @@ TransformStatic = require('./TransformStatic'), Transform = require('./Transform'), Bounds = require('./Bounds'), - math = require('../math'), - _tempDisplayObjectParent = new DisplayObject(); + math = require('../math');//, + //_tempDisplayObjectParent = new DisplayObject(); /** * The base class for all objects that are rendered on the screen. @@ -15,99 +15,374 @@ * @mixes PIXI.interaction.interactiveTarget * @memberof PIXI */ -function DisplayObject() -{ - EventEmitter.call(this); +class DisplayObject extends EventEmitter { + constructor() + { + super(); - var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; + var TransformClass = CONST.TRANSFORM_MODE.DEFAULT === CONST.TRANSFORM_MODE.STATIC ? TransformStatic : Transform; - //TODO: need to create Transform from factory - /** - * World transform and local transform of this object. - * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + this.tempDisplayObjectParent = null; + + //TODO: need to create Transform from factory + /** + * World transform and local transform of this object. + * This will be reworked in v4.1, please do not use it yet unless you know what are you doing! + * + * @member {PIXI.TransformBase} + */ + this.transform = new TransformClass(); + + /** + * The opacity of the object. + * + * @member {number} + */ + this.alpha = 1; + + /** + * The visibility of the object. If false the object will not be drawn, and + * the updateTransform function will not be called. + * + * @member {boolean} + */ + this.visible = true; + + /** + * Can this object be rendered, if false the object will not be drawn but the updateTransform + * methods will still be called. + * + * @member {boolean} + */ + this.renderable = true; + + /** + * The display object container that contains this display object. + * + * @member {PIXI.Container} + * @readonly + */ + this.parent = null; + + /** + * The multiplied alpha of the displayObject + * + * @member {number} + * @readonly + */ + this.worldAlpha = 1; + + /** + * The area the filter is applied to. This is used as more of an optimisation + * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * + * Also works as an interaction mask + * + * @member {PIXI.Rectangle} + */ + this.filterArea = null; + + this._filters = null; + this._enabledFilters = null; + + /** + * The bounds object, this is used to calculate and store the bounds of the displayObject + * + * @member {PIXI.Rectangle} + * @private + */ + this._bounds = new Bounds(); + this._boundsID = 0; + this._lastBoundsID = -1; + this._boundsRect = null; + this._localBoundsRect = null; + + /** + * The original, cached mask of the object + * + * @member {PIXI.Rectangle} + * @private + */ + this._mask = null; + + } + + get _tempDisplayObjectParent() { + if (this.tempDisplayObjectParent === null) { + this.tempDisplayObjectParent = new DisplayObject(); + } + return this.tempDisplayObjectParent; + } + + /* + * Updates the object transform for rendering * - * @member {PIXI.TransformBase} + * TODO - Optimization pass! */ - this.transform = new TransformClass(); + updateTransform() + { + this.transform.updateTransform(this.parent.transform); + // multiply the alphas.. + this.worldAlpha = this.alpha * this.parent.worldAlpha; + + this._bounds.updateID++; + } /** - * The opacity of the object. - * - * @member {number} + * recursively updates transform of all objects from the root to this one + * internal function for toLocal() */ - this.alpha = 1; + _recursivePostUpdateTransform() + { + if (this.parent) + { + this.parent._recursivePostUpdateTransform(); + this.transform.updateTransform(this.parent.transform); + } + else + { + this.transform.updateTransform(this._tempDisplayObjectParent.transform); + } + } /** - * The visibility of the object. If false the object will not be drawn, and - * the updateTransform function will not be called. * - * @member {boolean} + * + * Retrieves the bounds of the displayObject as a rectangle object. + * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.visible = true; + getBounds(skipUpdate, rect) + { + if(!skipUpdate) + { + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.parent.transform._worldID++; + this.updateTransform(); + this.parent = null; + } + else + { + this._recursivePostUpdateTransform(); + this.updateTransform(); + } + } + + if(this._boundsID !== this._lastBoundsID) + { + this.calculateBounds(); + } + + if(!rect) + { + if(!this._boundsRect) + { + this._boundsRect = new math.Rectangle(); + } + + rect = this._boundsRect; + } + + return this._bounds.getRectangle(rect); + } /** - * Can this object be rendered, if false the object will not be drawn but the updateTransform - * methods will still be called. - * - * @member {boolean} + * Retrieves the local bounds of the displayObject as a rectangle object + * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation + * @return {PIXI.Rectangle} the rectangular bounding area */ - this.renderable = true; + getLocalBounds(rect) + { + var transformRef = this.transform; + var parentRef = this.parent; + + this.parent = null; + this.transform = this._tempDisplayObjectParent.transform; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + var bounds = this.getBounds(false, rect); + + this.parent = parentRef; + this.transform = transformRef; + + return bounds; + } /** - * The display object container that contains this display object. + * Calculates the global position of the display object * - * @member {PIXI.Container} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @return {PIXI.Point} A point object representing the position of this object */ - this.parent = null; + toGlobal(position, point, skipUpdate) + { + if(!skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // don't need to update the lot + return this.worldTransform.apply(position, point); + } /** - * The multiplied alpha of the displayObject + * Calculates the local position of the display object relative to another point * - * @member {number} - * @readonly + * @param position {PIXI.Point} The world origin to calculate from + * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) + * @return {PIXI.Point} A point object representing the position of this object */ - this.worldAlpha = 1; + toLocal(position, from, point, skipUpdate) + { + if (from) + { + position = from.toGlobal(position, point, skipUpdate); + } + + if(! skipUpdate) + { + this._recursivePostUpdateTransform(); + + // this parent check is for just in case the item is a root object. + // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly + // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) + if(!this.parent) + { + this.parent = this._tempDisplayObjectParent; + this.displayObjectUpdateTransform(); + this.parent = null; + } + else + { + this.displayObjectUpdateTransform(); + } + } + + // simply apply the matrix.. + return this.worldTransform.applyInverse(position, point); + } /** - * The area the filter is applied to. This is used as more of an optimisation - * rather than figuring out the dimensions of the displayObject each frame you can set this rectangle + * Renders the object using the WebGL renderer * - * Also works as an interaction mask - * - * @member {PIXI.Rectangle} + * @param renderer {PIXI.WebGLRenderer} The renderer */ - this.filterArea = null; - - this._filters = null; - this._enabledFilters = null; + renderWebGL(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The bounds object, this is used to calculate and store the bounds of the displayObject + * Renders the object using the Canvas renderer * - * @member {PIXI.Rectangle} - * @private + * @param renderer {PIXI.CanvasRenderer} The renderer */ - this._bounds = new Bounds(); - this._boundsID = 0; - this._lastBoundsID = -1; - this._boundsRect = null; - this._localBoundsRect = null; + renderCanvas(renderer) // jshint unused:false + { + // OVERWRITE; + } /** - * The original, cached mask of the object + * Set the parent Container of this DisplayObject * - * @member {PIXI.Rectangle} - * @private + * @param container {PIXI.Container} The Container to add this DisplayObject to + * @return {PIXI.Container} The Container that this DisplayObject was added to */ - this._mask = null; + setParent(container) + { + if (!container || !container.addChild) + { + throw new Error('setParent: Argument must be a Container'); + } + container.addChild(this); + return container; + } + + /** + * Convenience function to set the postion, scale, skew and pivot at once. + * + * @param [x=0] {number} The X position + * @param [y=0] {number} The Y position + * @param [scaleX=1] {number} The X scale value + * @param [scaleY=1] {number} The Y scale value + * @param [rotation=0] {number} The rotation + * @param [skewX=0] {number} The X skew value + * @param [skewY=0] {number} The Y skew value + * @param [pivotX=0] {number} The X pivot value + * @param [pivotY=0] {number} The Y pivot value + * @return {PIXI.DisplayObject} The DisplayObject instance + */ + setTransform(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line + { + this.position.x = x || 0; + this.position.y = y || 0; + this.scale.x = !scaleX ? 1 : scaleX; + this.scale.y = !scaleY ? 1 : scaleY; + this.rotation = rotation || 0; + this.skew.x = skewX || 0; + this.skew.y = skewY || 0; + this.pivot.x = pivotX || 0; + this.pivot.y = pivotY || 0; + return this; + } + + /** + * Base destroy method for generic display objects. This will automatically + * remove the display object from its parent Container as well as remove + * all current event listeners and internal references. Do not use a DisplayObject + * after calling `destroy`. + */ + destroy() + { + this.removeAllListeners(); + if (this.parent) + { + this.parent.removeChild(this); + } + this.transform = null; + + this.parent = null; + + this._bounds = null; + this._currentBounds = null; + this._mask = null; + + this.filterArea = null; + + this.interactive = false; + this.interactiveChildren = false; + } } -// constructor -DisplayObject.prototype = Object.create(EventEmitter.prototype); -DisplayObject.prototype.constructor = DisplayObject; module.exports = DisplayObject; @@ -335,272 +610,5 @@ }); -/* - * Updates the object transform for rendering - * - * TODO - Optimization pass! - */ -DisplayObject.prototype.updateTransform = function () -{ - this.transform.updateTransform(this.parent.transform); - // multiply the alphas.. - this.worldAlpha = this.alpha * this.parent.worldAlpha; - - this._bounds.updateID++; -}; - // performance increase to avoid using call.. (10x faster) DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform; - -/** - * recursively updates transform of all objects from the root to this one - * internal function for toLocal() - */ -DisplayObject.prototype._recursivePostUpdateTransform = function() -{ - if (this.parent) - { - this.parent._recursivePostUpdateTransform(); - this.transform.updateTransform(this.parent.transform); - } - else - { - this.transform.updateTransform(_tempDisplayObjectParent.transform); - } -}; - -/** - * - * - * Retrieves the bounds of the displayObject as a rectangle object. - * @param skipUpdate {boolean} setting to true will stop the transforms of the scene graph from being updated. This means the calculation returned MAY be out of date BUT will give you a nice performance boost - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getBounds = function (skipUpdate, rect) -{ - if(!skipUpdate) - { - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.parent.transform._worldID++; - this.updateTransform(); - this.parent = null; - } - else - { - this._recursivePostUpdateTransform(); - this.updateTransform(); - } - } - - if(this._boundsID !== this._lastBoundsID) - { - this.calculateBounds(); - } - - if(!rect) - { - if(!this._boundsRect) - { - this._boundsRect = new math.Rectangle(); - } - - rect = this._boundsRect; - } - - return this._bounds.getRectangle(rect); -}; - -/** - * Retrieves the local bounds of the displayObject as a rectangle object - * @param rect {PIXI.Rectangle} Optional rectangle to store the result of the bounds calculation - * @return {PIXI.Rectangle} the rectangular bounding area - */ -DisplayObject.prototype.getLocalBounds = function (rect) -{ - var transformRef = this.transform; - var parentRef = this.parent; - - this.parent = null; - this.transform = _tempDisplayObjectParent.transform; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - var bounds = this.getBounds(false, rect); - - this.parent = parentRef; - this.transform = transformRef; - - return bounds; -}; - -/** - * Calculates the global position of the display object - * - * @param position {PIXI.Point} The world origin to calculate from - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toGlobal = function (position, point, skipUpdate) -{ - if(!skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // don't need to update the lot - return this.worldTransform.apply(position, point); -}; - -/** - * Calculates the local position of the display object relative to another point - * - * @param position {PIXI.Point} The world origin to calculate from - * @param [from] {PIXI.DisplayObject} The DisplayObject to calculate the global position from - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new Point) - * @return {PIXI.Point} A point object representing the position of this object - */ -DisplayObject.prototype.toLocal = function (position, from, point, skipUpdate) -{ - if (from) - { - position = from.toGlobal(position, point, skipUpdate); - } - - if(! skipUpdate) - { - this._recursivePostUpdateTransform(); - - // this parent check is for just in case the item is a root object. - // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly - // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) - if(!this.parent) - { - this.parent = _tempDisplayObjectParent; - this.displayObjectUpdateTransform(); - this.parent = null; - } - else - { - this.displayObjectUpdateTransform(); - } - } - - // simply apply the matrix.. - return this.worldTransform.applyInverse(position, point); -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -DisplayObject.prototype.renderWebGL = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - */ -DisplayObject.prototype.renderCanvas = function (renderer) // jshint unused:false -{ - // OVERWRITE; -}; - -/** - * Set the parent Container of this DisplayObject - * - * @param container {PIXI.Container} The Container to add this DisplayObject to - * @return {PIXI.Container} The Container that this DisplayObject was added to - */ -DisplayObject.prototype.setParent = function (container) -{ - if (!container || !container.addChild) - { - throw new Error('setParent: Argument must be a Container'); - } - - container.addChild(this); - return container; -}; - -/** - * Convenience function to set the postion, scale, skew and pivot at once. - * - * @param [x=0] {number} The X position - * @param [y=0] {number} The Y position - * @param [scaleX=1] {number} The X scale value - * @param [scaleY=1] {number} The Y scale value - * @param [rotation=0] {number} The rotation - * @param [skewX=0] {number} The X skew value - * @param [skewY=0] {number} The Y skew value - * @param [pivotX=0] {number} The X pivot value - * @param [pivotY=0] {number} The Y pivot value - * @return {PIXI.DisplayObject} The DisplayObject instance - */ -DisplayObject.prototype.setTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, pivotX, pivotY) //jshint ignore:line -{ - this.position.x = x || 0; - this.position.y = y || 0; - this.scale.x = !scaleX ? 1 : scaleX; - this.scale.y = !scaleY ? 1 : scaleY; - this.rotation = rotation || 0; - this.skew.x = skewX || 0; - this.skew.y = skewY || 0; - this.pivot.x = pivotX || 0; - this.pivot.y = pivotY || 0; - return this; -}; - -/** - * Base destroy method for generic display objects. This will automatically - * remove the display object from its parent Container as well as remove - * all current event listeners and internal references. Do not use a DisplayObject - * after calling `destroy`. - */ -DisplayObject.prototype.destroy = function () -{ - this.removeAllListeners(); - if (this.parent) - { - this.parent.removeChild(this); - } - this.transform = null; - - this.parent = null; - - this._bounds = null; - this._currentBounds = null; - this._mask = null; - - this.filterArea = null; - - this.interactive = false; - this.interactiveChildren = false; -}; diff --git a/src/core/display/Transform.js b/src/core/display/Transform.js index 913a5a1..6d2f98c 100644 --- a/src/core/display/Transform.js +++ b/src/core/display/Transform.js @@ -10,129 +10,128 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function Transform() -{ - TransformBase.call(this); +class Transform extends TransformBase { + constructor() + { + super(); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.Point} - */ - this.position = new math.Point(0,0); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.Point} + */ + this.position = new math.Point(0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.Point} + */ + this.scale = new math.Point(1,1); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.Point} + */ + this.pivot = new math.Point(0,0); + + /** + * The rotation value of the object, in radians + * + * @member {Number} + * @private + */ + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + } + + updateSkew() + { + this._cy = Math.cos(this.skew.y); + this._sy = Math.sin(this.skew.y); + this._nsx = Math.sin(this.skew.x); + this._cx = Math.cos(this.skew.x); + } /** - * The scale factor of the object. - * - * @member {PIXI.Point} + * Updates only local matrix */ - this.scale = new math.Point(1,1); + updateLocalTransform() { + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object */ - this.skew = new math.ObservablePoint(this.updateSkew, this, 0,0); + updateTransform(parentTransform) + { + + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + var a, b, c, d; + + a = this._cr * this.scale.x; + b = this._sr * this.scale.x; + c = -this._sr * this.scale.y; + d = this._cr * this.scale.y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); + lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); + + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } /** - * The pivot point of the displayObject that it rotates around - * - * @member {PIXI.Point} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.pivot = new math.Point(0,0); + setFromMatrix(matrix) + { + matrix.decompose(this); + } - /** - * The rotation value of the object, in radians - * - * @member {Number} - * @private - */ - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); } -Transform.prototype = Object.create(TransformBase.prototype); -Transform.prototype.constructor = Transform; - -Transform.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew.y); - this._sy = Math.sin(this.skew.y); - this._nsx = Math.sin(this.skew.x); - this._cx = Math.cos(this.skew.x); -}; - -/** - * Updates only local matrix - */ -Transform.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - */ -Transform.prototype.updateTransform = function (parentTransform) -{ - - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - var a, b, c, d; - - a = this._cr * this.scale.x; - b = this._sr * this.scale.x; - c = -this._sr * this.scale.y; - d = this._cr * this.scale.y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position.x - (this.pivot.x * lt.a + this.pivot.y * lt.c); - lt.ty = this.position.y - (this.pivot.x * lt.b + this.pivot.y * lt.d); - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -Transform.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); -}; - - Object.defineProperties(Transform.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/display/TransformBase.js b/src/core/display/TransformBase.js index 1472515..64824df 100644 --- a/src/core/display/TransformBase.js +++ b/src/core/display/TransformBase.js @@ -7,55 +7,56 @@ * @class * @memberof PIXI */ -function TransformBase() -{ +class TransformBase { + constructor() + { + /** + * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * + * @member {PIXI.Matrix} + */ + this.worldTransform = new math.Matrix(); + /** + * The local matrix transform + * + * @member {PIXI.Matrix} + */ + this.localTransform = new math.Matrix(); + + this._worldID = 0; + } + /** - * The global matrix transform. It can be swapped temporarily by some functions like getLocalBounds() + * TransformBase does not have decomposition, so this function wont do anything + */ + updateLocalTransform() { // jshint unused:false + + } + + /** + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object * - * @member {PIXI.Matrix} */ - this.worldTransform = new math.Matrix(); - /** - * The local matrix transform - * - * @member {PIXI.Matrix} - */ - this.localTransform = new math.Matrix(); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; - this._worldID = 0; + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._worldID ++; + } + } -TransformBase.prototype.constructor = TransformBase; - -/** - * TransformBase does not have decomposition, so this function wont do anything - */ -TransformBase.prototype.updateLocalTransform = function() { // jshint unused:false - -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.TransformBase} The transform of the parent of this object - * - */ -TransformBase.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._worldID ++; -}; - /** * Updates the values of the object and applies the parent's transform. * @param parentTransform {PIXI.Transform} The transform of the parent of this object diff --git a/src/core/display/TransformStatic.js b/src/core/display/TransformStatic.js index e482e21..c30e789 100644 --- a/src/core/display/TransformStatic.js +++ b/src/core/display/TransformStatic.js @@ -8,158 +8,158 @@ * @extends PIXI.TransformBase * @memberof PIXI */ -function TransformStatic() -{ - TransformBase.call(this); - /** - * The coordinate of the object relative to the local coordinates of the parent. - * - * @member {PIXI.ObservablePoint} - */ - this.position = new math.ObservablePoint(this.onChange, this,0,0); +class TransformStatic extends TransformBase { + constructor() + { + super(); + /** + * The coordinate of the object relative to the local coordinates of the parent. + * + * @member {PIXI.ObservablePoint} + */ + this.position = new math.ObservablePoint(this.onChange, this,0,0); + + /** + * The scale factor of the object. + * + * @member {PIXI.ObservablePoint} + */ + this.scale = new math.ObservablePoint(this.onChange, this,1,1); + + /** + * The pivot point of the displayObject that it rotates around + * + * @member {PIXI.ObservablePoint} + */ + this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + + /** + * The skew amount, on the x and y axis. + * + * @member {PIXI.ObservablePoint} + */ + this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + + this._rotation = 0; + + this._sr = Math.sin(0); + this._cr = Math.cos(0); + this._cy = Math.cos(0);//skewY); + this._sy = Math.sin(0);//skewY); + this._nsx = Math.sin(0);//skewX); + this._cx = Math.cos(0);//skewX); + + this._localID = 0; + this._currentLocalID = 0; + } + + onChange() + { + this._localID ++; + } + + updateSkew() + { + this._cy = Math.cos(this.skew._y); + this._sy = Math.sin(this.skew._y); + this._nsx = Math.sin(this.skew._x); + this._cx = Math.cos(this.skew._x); + + this._localID ++; + } /** - * The scale factor of the object. - * - * @member {PIXI.ObservablePoint} + * Updates only local matrix */ - this.scale = new math.ObservablePoint(this.onChange, this,1,1); + updateLocalTransform() { + var lt = this.localTransform; + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + } /** - * The pivot point of the displayObject that it rotates around + * Updates the values of the object and applies the parent's transform. + * @param parentTransform {PIXI.Transform} The transform of the parent of this object * - * @member {PIXI.ObservablePoint} */ - this.pivot = new math.ObservablePoint(this.onChange, this,0, 0); + updateTransform(parentTransform) + { + var pt = parentTransform.worldTransform; + var wt = this.worldTransform; + var lt = this.localTransform; + + if(this._localID !== this._currentLocalID) + { + // get the matrix values of the displayobject based on its transform properties.. + var a,b,c,d; + + a = this._cr * this.scale._x; + b = this._sr * this.scale._x; + c = -this._sr * this.scale._y; + d = this._cr * this.scale._y; + + lt.a = this._cy * a + this._sy * c; + lt.b = this._cy * b + this._sy * d; + lt.c = this._nsx * a + this._cx * c; + lt.d = this._nsx * b + this._cx * d; + + lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); + lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); + this._currentLocalID = this._localID; + + // force an update.. + this._parentID = -1; + } + + if(this._parentID !== parentTransform._worldID) + { + // concat the parent matrix with the objects transform. + wt.a = lt.a * pt.a + lt.b * pt.c; + wt.b = lt.a * pt.b + lt.b * pt.d; + wt.c = lt.c * pt.a + lt.d * pt.c; + wt.d = lt.c * pt.b + lt.d * pt.d; + wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; + wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; + + this._parentID = parentTransform._worldID; + + // update the id of the transform.. + this._worldID ++; + } + } /** - * The skew amount, on the x and y axis. - * - * @member {PIXI.ObservablePoint} + * Decomposes a matrix and sets the transforms properties based on it. + * @param {PIXI.Matrix} The matrix to decompose */ - this.skew = new math.ObservablePoint(this.updateSkew, this,0, 0); + setFromMatrix(matrix) + { + matrix.decompose(this); + this._localID ++; + } - this._rotation = 0; - - this._sr = Math.sin(0); - this._cr = Math.cos(0); - this._cy = Math.cos(0);//skewY); - this._sy = Math.sin(0);//skewY); - this._nsx = Math.sin(0);//skewX); - this._cx = Math.cos(0);//skewX); - - this._localID = 0; - this._currentLocalID = 0; } -TransformStatic.prototype = Object.create(TransformBase.prototype); -TransformStatic.prototype.constructor = TransformStatic; - -TransformStatic.prototype.onChange = function () -{ - this._localID ++; -}; - -TransformStatic.prototype.updateSkew = function () -{ - this._cy = Math.cos(this.skew._y); - this._sy = Math.sin(this.skew._y); - this._nsx = Math.sin(this.skew._x); - this._cx = Math.cos(this.skew._x); - - this._localID ++; -}; - -/** - * Updates only local matrix - */ -TransformStatic.prototype.updateLocalTransform = function() { - var lt = this.localTransform; - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } -}; - -/** - * Updates the values of the object and applies the parent's transform. - * @param parentTransform {PIXI.Transform} The transform of the parent of this object - * - */ -TransformStatic.prototype.updateTransform = function (parentTransform) -{ - var pt = parentTransform.worldTransform; - var wt = this.worldTransform; - var lt = this.localTransform; - - if(this._localID !== this._currentLocalID) - { - // get the matrix values of the displayobject based on its transform properties.. - var a,b,c,d; - - a = this._cr * this.scale._x; - b = this._sr * this.scale._x; - c = -this._sr * this.scale._y; - d = this._cr * this.scale._y; - - lt.a = this._cy * a + this._sy * c; - lt.b = this._cy * b + this._sy * d; - lt.c = this._nsx * a + this._cx * c; - lt.d = this._nsx * b + this._cx * d; - - lt.tx = this.position._x - (this.pivot._x * lt.a + this.pivot._y * lt.c); - lt.ty = this.position._y - (this.pivot._x * lt.b + this.pivot._y * lt.d); - this._currentLocalID = this._localID; - - // force an update.. - this._parentID = -1; - } - - if(this._parentID !== parentTransform._worldID) - { - // concat the parent matrix with the objects transform. - wt.a = lt.a * pt.a + lt.b * pt.c; - wt.b = lt.a * pt.b + lt.b * pt.d; - wt.c = lt.c * pt.a + lt.d * pt.c; - wt.d = lt.c * pt.b + lt.d * pt.d; - wt.tx = lt.tx * pt.a + lt.ty * pt.c + pt.tx; - wt.ty = lt.tx * pt.b + lt.ty * pt.d + pt.ty; - - this._parentID = parentTransform._worldID; - - // update the id of the transform.. - this._worldID ++; - } -}; - -/** - * Decomposes a matrix and sets the transforms properties based on it. - * @param {PIXI.Matrix} The matrix to decompose - */ -TransformStatic.prototype.setFromMatrix = function (matrix) -{ - matrix.decompose(this); - this._localID ++; -}; - Object.defineProperties(TransformStatic.prototype, { /** * The rotation of the object in radians. diff --git a/src/core/graphics/Graphics.js b/src/core/graphics/Graphics.js index 313a716..98cbfca 100644 --- a/src/core/graphics/Graphics.js +++ b/src/core/graphics/Graphics.js @@ -23,1032 +23,1032 @@ * @extends PIXI.Container * @memberof PIXI */ -function Graphics() -{ - Container.call(this); +class Graphics extends Container { + constructor() + { + super(); + + /** + * 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 {PIXI.GraphicsData[]} + * @private + */ + this.graphicsData = []; + + /** + * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * + * @member {number} + * @default 0xFFFFFF + */ + this.tint = 0xFFFFFF; + + /** + * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. + * + * @member {number} + * @private + * @default 0xFFFFFF + */ + this._prevTint = 0xFFFFFF; + + /** + * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL; + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * Current path + * + * @member {PIXI.GraphicsData} + * @private + */ + this.currentPath = null; + + /** + * Array containing some WebGL-related properties used by the WebGL renderer. + * + * @member {object} + * @private + */ + // TODO - _webgl should use a prototype object, not a random undocumented object... + this._webGL = {}; + + /** + * Whether this shape is being used as a mask. + * + * @member {boolean} + */ + this.isMask = false; + + /** + * The bounds' padding used for bounds calculation. + * + * @member {number} + */ + this.boundsPadding = 0; + + /** + * A cache of the local bounds to prevent recalculation. + * + * @member {PIXI.Rectangle} + * @private + */ + this._localBounds = new Bounds(); + + /** + * 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 = 0; + + /** + * Used to detect if we need to do a fast rect check using the id compare method + * @type {Number} + */ + this.fastRectDirty = -1; + + /** + * Used to detect if we clear the graphics webGL data + * @type {Number} + */ + this.clearDirty = 0; + + /** + * Used to detect if we we need to recalculate local bounds + * @type {Number} + */ + this.boundsDirty = -1; + + /** + * Used to detect if the cached sprite object needs to be updated. + * + * @member {boolean} + * @private + */ + this.cachedSpriteDirty = false; + + + this._spriteRect = null; + this._fastRect = false; + + /** + * 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. + * + * @name cacheAsBitmap + * @member {boolean} + * @memberof PIXI.Graphics# + * @default false + */ + } /** - * The alpha value used when filling the Graphics object. + * Creates a new Graphics object with the same values as this one. + * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) * - * @member {number} - * @default 1 + * @return {PIXI.Graphics} A clone of the graphics object */ - this.fillAlpha = 1; + clone() + { + var clone = new Graphics(); + + clone.renderable = this.renderable; + clone.fillAlpha = this.fillAlpha; + clone.lineWidth = this.lineWidth; + clone.lineColor = this.lineColor; + clone.tint = this.tint; + clone.blendMode = this.blendMode; + clone.isMask = this.isMask; + clone.boundsPadding = this.boundsPadding; + clone.dirty = 0; + clone.cachedSpriteDirty = this.cachedSpriteDirty; + + // copy graphics data + for (var i = 0; i < this.graphicsData.length; ++i) + { + clone.graphicsData.push(this.graphicsData[i].clone()); + } + + clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; + + clone.updateLocalBounds(); + + return clone; + } /** - * The width (thickness) of any lines drawn. + * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. * - * @member {number} - * @default 0 + * @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 {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineWidth = 0; + lineStyle(lineWidth, color, alpha) + { + this.lineWidth = lineWidth || 0; + this.lineColor = color || 0; + this.lineAlpha = (alpha === undefined) ? 1 : alpha; + + if (this.currentPath) + { + if (this.currentPath.shape.points.length) + { + // halfway through a line? start a new one! + var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); + shape.closed = false; + this.drawShape(shape); + } + 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; + } /** - * The color of any lines drawn. + * Moves the current drawing position to x, y. * - * @member {string} - * @default 0 + * @param x {number} the X coordinate to move to + * @param y {number} the Y coordinate to move to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls */ - this.lineColor = 0; + moveTo(x, y) + { + var shape = new math.Polygon([x,y]); + shape.closed = false; + this.drawShape(shape); + + return this; + } /** - * Graphics data + * 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). * - * @member {PIXI.GraphicsData[]} + * @param x {number} the X coordinate to draw to + * @param y {number} the Y coordinate to draw to + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + lineTo(x, y) + { + this.currentPath.shape.points.push(x, y); + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + quadraticCurveTo(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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + bezierCurveTo(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 points = this.currentPath.shape.points; + + var fromX = points[points.length-2]; + var fromY = points[points.length-1]; + + points.length -= 2; + + bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); + + this.dirty++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arcTo(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++; + + 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + arc(cx, cy, radius, startAngle, endAngle, anticlockwise) + { + anticlockwise = anticlockwise || false; + + 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; + + if(sweep === 0) + { + return this; + } + + var startX = cx + Math.cos(startAngle) * radius; + var startY = cy + Math.sin(startAngle) * radius; + + if (this.currentPath) + { + this.currentPath.shape.points.push(startX, startY); + } + else + { + this.moveTo(startX, startY); + } + + var points = this.currentPath.shape.points; + + 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++; + + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + beginFill(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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + endFill() + { + 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRect( x, y, width, height ) + { + this.drawShape(new math.Rectangle(x,y, width, height)); + + return this; + } + + /** + * + * @param x {number} The X coord of the top-left of the rectangle + * @param y {number} The Y coord of the top-left of the rectangle + * @param width {number} The width of the rectangle + * @param height {number} The height of the rectangle + * @param radius {number} Radius of the rectangle corners + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawRoundedRect( x, y, width, height, radius ) + { + this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); + + return this; + } + + /** + * Draws a circle. + * + * @param x {number} The X coordinate of the center of the circle + * @param y {number} The Y coordinate of the center of the circle + * @param radius {number} The radius of the circle + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawCircle(x, y, radius) + { + this.drawShape(new math.Circle(x,y, radius)); + + return this; + } + + /** + * Draws an ellipse. + * + * @param x {number} The X coordinate of the center of the ellipse + * @param y {number} The Y coordinate of the center of the ellipse + * @param width {number} The half width of the ellipse + * @param height {number} The half height of the ellipse + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawEllipse(x, y, width, height) + { + this.drawShape(new math.Ellipse(x, y, width, height)); + + return this; + } + + /** + * Draws a polygon using the given path. + * + * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + drawPolygon(path) + { + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = path; + + var closed = true; + + if (points instanceof math.Polygon) + { + closed = points.closed; + points = points.points; + } + + if (!Array.isArray(points)) + { + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var i = 0; i < points.length; ++i) + { + points[i] = arguments[i]; + } + } + + var shape = new math.Polygon(points); + shape.closed = closed; + + this.drawShape(shape); + + return this; + } + + /** + * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. + * + * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls + */ + clear() + { + this.lineWidth = 0; + this.filling = false; + + this.dirty++; + this.clearDirty++; + this.graphicsData = []; + + return this; + } + + /** + * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor + * @returns {boolean} + */ + isFastRect() { + return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} * @private */ - this.graphicsData = []; + _renderWebGL(renderer) + { + // if the sprite is not visible or the alpha is 0 then no need to render this element + if(this.dirty !== this.fastRectDirty) + { + this.fastRectDirty = this.dirty; + this._fastRect = this.isFastRect(); + } + + //TODO this check can be moved to dirty? + if(this._fastRect) + { + this._renderSpriteRect(renderer); + } + else + { + renderer.setObjectRenderer(renderer.plugins.graphics); + renderer.plugins.graphics.render(this); + } + + } + + _renderSpriteRect(renderer) + { + var rect = this.graphicsData[0].shape; + if(!this._spriteRect) + { + if(!Graphics._SPRITE_TEXTURE) + { + Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); + + var currentRenderTarget = renderer._activeRenderTarget; + renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); + renderer.clear([1,1,1,1]); + renderer.bindRenderTarget(currentRenderTarget); + } + + this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); + } + if (this.tint === 0xffffff) { + this._spriteRect.tint = this.graphicsData[0].fillColor; + } else { + var t1 = tempColor1; + var t2 = tempColor2; + utils.hex2rgb(this.graphicsData[0].fillColor, t1); + utils.hex2rgb(this.tint, t2); + t1[0] *= t2[0]; + t1[1] *= t2[1]; + t1[2] *= t2[2]; + this._spriteRect.tint = utils.rgb2hex(t1); + } + this._spriteRect.alpha = this.graphicsData[0].fillAlpha; + this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; + + Graphics._SPRITE_TEXTURE._frame.width = rect.width; + Graphics._SPRITE_TEXTURE._frame.height = rect.height; + + this._spriteRect.transform.worldTransform = this.transform.worldTransform; + + this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); + this._spriteRect.onAnchorUpdate(); + + this._spriteRect._renderWebGL(renderer); + } /** - * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint. + * Renders the object using the Canvas renderer * - * @member {number} - * @default 0xFFFFFF - */ - this.tint = 0xFFFFFF; - - /** - * The previous tint applied to the graphic shape. Used to compare to the current tint and check if theres change. - * - * @member {number} - * @private - * @default 0xFFFFFF - */ - this._prevTint = 0xFFFFFF; - - /** - * The blend mode to be applied to the graphic shape. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL; - * @see PIXI.BLEND_MODES - */ - this.blendMode = CONST.BLEND_MODES.NORMAL; - - /** - * Current path - * - * @member {PIXI.GraphicsData} + * @param renderer {PIXI.CanvasRenderer} * @private */ - this.currentPath = null; + _renderCanvas(renderer) + { + if (this.isMask === true) + { + return; + } + + renderer.plugins.graphics.render(this); + } /** - * Array containing some WebGL-related properties used by the WebGL renderer. + * Retrieves the bounds of the graphic shape as a rectangle object * - * @member {object} - * @private + * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this + * object's worldTransform. + * @return {PIXI.Rectangle} the rectangular bounding area */ - // TODO - _webgl should use a prototype object, not a random undocumented object... - this._webGL = {}; + _calculateBounds() + { + if (!this.renderable) + { + return; + } + + if (this.boundsDirty !== this.dirty) + { + this.boundsDirty = this.dirty; + this.updateLocalBounds(); + + this.dirty++; + this.cachedSpriteDirty = true; + } + + var lb = this._localBounds; + this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); + } /** - * Whether this shape is being used as a mask. + * Tests if a point is inside this graphics object + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var graphicsData = this.graphicsData; + + for (var i = 0; i < graphicsData.length; i++) + { + var data = graphicsData[i]; + + if (!data.fill) + { + continue; + } + + // only deal with fills.. + if (data.shape) + { + if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) + { + return true; + } + } + } + + return false; + } + + /** + * Update the bounds of the object * - * @member {boolean} */ - this.isMask = false; + updateLocalBounds() + { + var minX = Infinity; + var maxX = -Infinity; + + var minY = Infinity; + var maxY = -Infinity; + + if (this.graphicsData.length) + { + var shape, points, x, y, w, h; + + for (var i = 0; i < this.graphicsData.length; i++) + { + var data = this.graphicsData[i]; + var type = data.type; + var lineWidth = data.lineWidth; + shape = data.shape; + + if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) + { + x = shape.x - lineWidth/2; + y = shape.y - lineWidth/2; + w = shape.width + lineWidth; + h = shape.height + lineWidth; + + minX = x < minX ? x : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y < minY ? y : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.CIRC) + { + x = shape.x; + y = shape.y; + w = shape.radius + lineWidth/2; + h = shape.radius + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else if (type === CONST.SHAPES.ELIP) + { + x = shape.x; + y = shape.y; + w = shape.width + lineWidth/2; + h = shape.height + lineWidth/2; + + minX = x - w < minX ? x - w : minX; + maxX = x + w > maxX ? x + w : maxX; + + minY = y - h < minY ? y - h : minY; + maxY = y + h > maxY ? y + h : maxY; + } + else + { + // POLY + points = shape.points; + + for (var j = 0; j < points.length; j += 2) + { + x = points[j]; + y = points[j+1]; + + minX = x-lineWidth < minX ? x-lineWidth : minX; + maxX = x+lineWidth > maxX ? x+lineWidth : maxX; + + minY = y-lineWidth < minY ? y-lineWidth : minY; + maxY = y+lineWidth > maxY ? y+lineWidth : maxY; + } + } + } + } + else + { + minX = 0; + maxX = 0; + minY = 0; + maxY = 0; + } + + var padding = this.boundsPadding; + + this._localBounds.minX = minX - padding; + this._localBounds.maxX = maxX + padding * 2; + + this._localBounds.minY = minY - padding; + this._localBounds.maxY = maxY + padding * 2; + } + /** - * The bounds' padding used for bounds calculation. + * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. * - * @member {number} + * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + * @return {PIXI.GraphicsData} The generated GraphicsData object. */ - this.boundsPadding = 0; + drawShape(shape) + { + if (this.currentPath) + { + // check current path! + if (this.currentPath.shape.points.length <= 2) + { + this.graphicsData.pop(); + } + } + + this.currentPath = null; + + var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); + + this.graphicsData.push(data); + + if (data.type === CONST.SHAPES.POLY) + { + data.shape.closed = data.shape.closed || this.filling; + this.currentPath = data; + } + + this.dirty++; + + return data; + } + + generateCanvasTexture(scaleMode, resolution) + { + resolution = resolution || 1; + + var bounds = this.getLocalBounds(); + + var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); + + if(!canvasRenderer) + { + canvasRenderer = new CanvasRenderer(); + } + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + canvasRenderer.render(this, canvasBuffer, false, tempMatrix); + + var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); + texture.baseTexture.resolution = resolution; + + return texture; + } + + closePath() + { + // ok so close path assumes next one is a hole! + var currentPath = this.currentPath; + if (currentPath && currentPath.shape) + { + currentPath.shape.close(); + } + return this; + } + + addHole() + { + // this is a hole! + var hole = this.graphicsData.pop(); + + this.currentPath = this.graphicsData[this.graphicsData.length-1]; + + this.currentPath.addHole(hole.shape); + this.currentPath = null; + + return this; + } /** - * A cache of the local bounds to prevent recalculation. - * - * @member {PIXI.Rectangle} - * @private + * Destroys the Graphics object. */ - this._localBounds = new Bounds(); + destroy() + { + super.destroy(arguments); - /** - * 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 = 0; + // destroy each of the GraphicsData objects + for (var i = 0; i < this.graphicsData.length; ++i) { + this.graphicsData[i].destroy(); + } - /** - * Used to detect if we need to do a fast rect check using the id compare method - * @type {Number} - */ - this.fastRectDirty = -1; + // for each webgl data entry, destroy the WebGLGraphicsData + for (var id in this._webgl) { + for (var j = 0; j < this._webgl[id].data.length; ++j) { + this._webgl[id].data[j].destroy(); + } + } - /** - * Used to detect if we clear the graphics webGL data - * @type {Number} - */ - this.clearDirty = 0; + if(this._spriteRect) + { + this._spriteRect.destroy(); + } + this.graphicsData = null; - /** - * Used to detect if we we need to recalculate local bounds - * @type {Number} - */ - this.boundsDirty = -1; + this.currentPath = null; + this._webgl = null; + this._localBounds = null; + } - /** - * Used to detect if the cached sprite object needs to be updated. - * - * @member {boolean} - * @private - */ - this.cachedSpriteDirty = false; - - - this._spriteRect = null; - this._fastRect = false; - - /** - * 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. - * - * @name cacheAsBitmap - * @member {boolean} - * @memberof PIXI.Graphics# - * @default false - */ } Graphics._SPRITE_TEXTURE = null; -// constructor -Graphics.prototype = Object.create(Container.prototype); -Graphics.prototype.constructor = Graphics; module.exports = Graphics; - -/** - * Creates a new Graphics object with the same values as this one. - * Note that the only the properties of the object are cloned, not its transform (position,scale,etc) - * - * @return {PIXI.Graphics} A clone of the graphics object - */ -Graphics.prototype.clone = function () -{ - var clone = new Graphics(); - - clone.renderable = this.renderable; - clone.fillAlpha = this.fillAlpha; - clone.lineWidth = this.lineWidth; - clone.lineColor = this.lineColor; - clone.tint = this.tint; - clone.blendMode = this.blendMode; - clone.isMask = this.isMask; - clone.boundsPadding = this.boundsPadding; - clone.dirty = 0; - clone.cachedSpriteDirty = this.cachedSpriteDirty; - - // copy graphics data - for (var i = 0; i < this.graphicsData.length; ++i) - { - clone.graphicsData.push(this.graphicsData[i].clone()); - } - - clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1]; - - clone.updateLocalBounds(); - - return clone; -}; - -/** - * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method. - * - * @param lineWidth {number} width of the line to draw, will update the objects stored style - * @param color {number} color of the line to draw, will update the objects stored style - * @param alpha {number} alpha of the line to draw, will update the objects stored style - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineStyle = function (lineWidth, color, alpha) -{ - this.lineWidth = lineWidth || 0; - this.lineColor = color || 0; - this.lineAlpha = (alpha === undefined) ? 1 : alpha; - - if (this.currentPath) - { - if (this.currentPath.shape.points.length) - { - // halfway through a line? start a new one! - var shape = new math.Polygon(this.currentPath.shape.points.slice(-2)); - shape.closed = false; - this.drawShape(shape); - } - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.moveTo = function (x, y) -{ - var shape = new math.Polygon([x,y]); - shape.closed = false; - this.drawShape(shape); - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.lineTo = function (x, y) -{ - this.currentPath.shape.points.push(x, y); - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 points = this.currentPath.shape.points; - - var fromX = points[points.length-2]; - var fromY = points[points.length-1]; - - points.length -= 2; - - bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points); - - this.dirty++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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++; - - 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=false] {boolean} Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise) -{ - anticlockwise = anticlockwise || false; - - 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.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40; - - if(sweep === 0) - { - return this; - } - - var startX = cx + Math.cos(startAngle) * radius; - var startY = cy + Math.sin(startAngle) * radius; - - if (this.currentPath) - { - this.currentPath.shape.points.push(startX, startY); - } - else - { - this.moveTo(startX, startY); - } - - var points = this.currentPath.shape.points; - - 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++; - - 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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -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 {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRect = function ( x, y, width, height ) -{ - this.drawShape(new math.Rectangle(x,y, width, height)); - - return this; -}; - -/** - * - * @param x {number} The X coord of the top-left of the rectangle - * @param y {number} The Y coord of the top-left of the rectangle - * @param width {number} The width of the rectangle - * @param height {number} The height of the rectangle - * @param radius {number} Radius of the rectangle corners - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawRoundedRect = function ( x, y, width, height, radius ) -{ - this.drawShape(new math.RoundedRectangle(x, y, width, height, radius)); - - return this; -}; - -/** - * Draws a circle. - * - * @param x {number} The X coordinate of the center of the circle - * @param y {number} The Y coordinate of the center of the circle - * @param radius {number} The radius of the circle - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawCircle = function (x, y, radius) -{ - this.drawShape(new math.Circle(x,y, radius)); - - return this; -}; - -/** - * Draws an ellipse. - * - * @param x {number} The X coordinate of the center of the ellipse - * @param y {number} The Y coordinate of the center of the ellipse - * @param width {number} The half width of the ellipse - * @param height {number} The half height of the ellipse - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawEllipse = function (x, y, width, height) -{ - this.drawShape(new math.Ellipse(x, y, width, height)); - - return this; -}; - -/** - * Draws a polygon using the given path. - * - * @param path {number[]|PIXI.Point[]} The path data used to construct the polygon. - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.drawPolygon = function (path) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = path; - - var closed = true; - - if (points instanceof math.Polygon) - { - closed = points.closed; - points = points.points; - } - - if (!Array.isArray(points)) - { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); - - for (var i = 0; i < points.length; ++i) - { - points[i] = arguments[i]; - } - } - - var shape = new math.Polygon(points); - shape.closed = closed; - - this.drawShape(shape); - - return this; -}; - -/** - * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings. - * - * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls - */ -Graphics.prototype.clear = function () -{ - this.lineWidth = 0; - this.filling = false; - - this.dirty++; - this.clearDirty++; - this.graphicsData = []; - - return this; -}; - -/** - * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and masked with gl.scissor - * @returns {boolean} - */ -Graphics.prototype.isFastRect = function() { - return this.graphicsData.length === 1 && this.graphicsData[0].shape.type === CONST.SHAPES.RECT && !this.graphicsData[0].lineWidth; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} - * @private - */ -Graphics.prototype._renderWebGL = function (renderer) -{ - // if the sprite is not visible or the alpha is 0 then no need to render this element - if(this.dirty !== this.fastRectDirty) - { - this.fastRectDirty = this.dirty; - this._fastRect = this.isFastRect(); - } - - //TODO this check can be moved to dirty? - if(this._fastRect) - { - this._renderSpriteRect(renderer); - } - else - { - renderer.setObjectRenderer(renderer.plugins.graphics); - renderer.plugins.graphics.render(this); - } - -}; - -Graphics.prototype._renderSpriteRect = function (renderer) -{ - var rect = this.graphicsData[0].shape; - if(!this._spriteRect) - { - if(!Graphics._SPRITE_TEXTURE) - { - Graphics._SPRITE_TEXTURE = RenderTexture.create(10, 10); - - var currentRenderTarget = renderer._activeRenderTarget; - renderer.bindRenderTexture(Graphics._SPRITE_TEXTURE); - renderer.clear([1,1,1,1]); - renderer.bindRenderTarget(currentRenderTarget); - } - - this._spriteRect = new Sprite(Graphics._SPRITE_TEXTURE); - } - if (this.tint === 0xffffff) { - this._spriteRect.tint = this.graphicsData[0].fillColor; - } else { - var t1 = tempColor1; - var t2 = tempColor2; - utils.hex2rgb(this.graphicsData[0].fillColor, t1); - utils.hex2rgb(this.tint, t2); - t1[0] *= t2[0]; - t1[1] *= t2[1]; - t1[2] *= t2[2]; - this._spriteRect.tint = utils.rgb2hex(t1); - } - this._spriteRect.alpha = this.graphicsData[0].fillAlpha; - this._spriteRect.worldAlpha = this.worldAlpha * this._spriteRect.alpha; - - Graphics._SPRITE_TEXTURE._frame.width = rect.width; - Graphics._SPRITE_TEXTURE._frame.height = rect.height; - - this._spriteRect.transform.worldTransform = this.transform.worldTransform; - - this._spriteRect.anchor.set(-rect.x / rect.width, -rect.y / rect.height); - this._spriteRect.onAnchorUpdate(); - - this._spriteRect._renderWebGL(renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} - * @private - */ -Graphics.prototype._renderCanvas = function (renderer) -{ - if (this.isMask === true) - { - return; - } - - renderer.plugins.graphics.render(this); -}; - -/** - * Retrieves the bounds of the graphic shape as a rectangle object - * - * @param [matrix] {PIXI.Matrix} The world transform matrix to use, defaults to this - * object's worldTransform. - * @return {PIXI.Rectangle} the rectangular bounding area - */ -Graphics.prototype._calculateBounds = function () -{ - if (!this.renderable) - { - return; - } - - if (this.boundsDirty !== this.dirty) - { - this.boundsDirty = this.dirty; - this.updateLocalBounds(); - - this.dirty++; - this.cachedSpriteDirty = true; - } - - var lb = this._localBounds; - this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY); -}; - -/** -* Tests if a point is inside this graphics object -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Graphics.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var graphicsData = this.graphicsData; - - for (var i = 0; i < graphicsData.length; i++) - { - var data = graphicsData[i]; - - if (!data.fill) - { - continue; - } - - // only deal with fills.. - if (data.shape) - { - if ( data.shape.contains( tempPoint.x, tempPoint.y ) ) - { - return true; - } - } - } - - return false; -}; - -/** - * Update the bounds of the object - * - */ -Graphics.prototype.updateLocalBounds = function () -{ - var minX = Infinity; - var maxX = -Infinity; - - var minY = Infinity; - var maxY = -Infinity; - - if (this.graphicsData.length) - { - var shape, points, x, y, w, h; - - for (var i = 0; i < this.graphicsData.length; i++) - { - var data = this.graphicsData[i]; - var type = data.type; - var lineWidth = data.lineWidth; - shape = data.shape; - - if (type === CONST.SHAPES.RECT || type === CONST.SHAPES.RREC) - { - x = shape.x - lineWidth/2; - y = shape.y - lineWidth/2; - w = shape.width + lineWidth; - h = shape.height + lineWidth; - - minX = x < minX ? x : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y < minY ? y : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.CIRC) - { - x = shape.x; - y = shape.y; - w = shape.radius + lineWidth/2; - h = shape.radius + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else if (type === CONST.SHAPES.ELIP) - { - x = shape.x; - y = shape.y; - w = shape.width + lineWidth/2; - h = shape.height + lineWidth/2; - - minX = x - w < minX ? x - w : minX; - maxX = x + w > maxX ? x + w : maxX; - - minY = y - h < minY ? y - h : minY; - maxY = y + h > maxY ? y + h : maxY; - } - else - { - // POLY - points = shape.points; - - for (var j = 0; j < points.length; j += 2) - { - x = points[j]; - y = points[j+1]; - - minX = x-lineWidth < minX ? x-lineWidth : minX; - maxX = x+lineWidth > maxX ? x+lineWidth : maxX; - - minY = y-lineWidth < minY ? y-lineWidth : minY; - maxY = y+lineWidth > maxY ? y+lineWidth : maxY; - } - } - } - } - else - { - minX = 0; - maxX = 0; - minY = 0; - maxY = 0; - } - - var padding = this.boundsPadding; - - this._localBounds.minX = minX - padding; - this._localBounds.maxX = maxX + padding * 2; - - this._localBounds.minY = minY - padding; - this._localBounds.maxY = maxY + padding * 2; -}; - - -/** - * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon. - * - * @param shape {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - * @return {PIXI.GraphicsData} The generated GraphicsData object. - */ -Graphics.prototype.drawShape = function (shape) -{ - if (this.currentPath) - { - // check current path! - if (this.currentPath.shape.points.length <= 2) - { - this.graphicsData.pop(); - } - } - - this.currentPath = null; - - var data = new GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape); - - this.graphicsData.push(data); - - if (data.type === CONST.SHAPES.POLY) - { - data.shape.closed = data.shape.closed || this.filling; - this.currentPath = data; - } - - this.dirty++; - - return data; -}; - -Graphics.prototype.generateCanvasTexture = function(scaleMode, resolution) -{ - resolution = resolution || 1; - - var bounds = this.getLocalBounds(); - - var canvasBuffer = new RenderTexture.create(bounds.width * resolution, bounds.height * resolution); - - if(!canvasRenderer) - { - canvasRenderer = new CanvasRenderer(); - } - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - canvasRenderer.render(this, canvasBuffer, false, tempMatrix); - - var texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode); - texture.baseTexture.resolution = resolution; - - return texture; -}; - -Graphics.prototype.closePath = function () -{ - // ok so close path assumes next one is a hole! - var currentPath = this.currentPath; - if (currentPath && currentPath.shape) - { - currentPath.shape.close(); - } - return this; -}; - -Graphics.prototype.addHole = function() -{ - // this is a hole! - var hole = this.graphicsData.pop(); - - this.currentPath = this.graphicsData[this.graphicsData.length-1]; - - this.currentPath.addHole(hole.shape); - this.currentPath = null; - - return this; -}; - -/** - * Destroys the Graphics object. - */ -Graphics.prototype.destroy = function () -{ - Container.prototype.destroy.apply(this, arguments); - - // destroy each of the GraphicsData objects - for (var i = 0; i < this.graphicsData.length; ++i) { - this.graphicsData[i].destroy(); - } - - // for each webgl data entry, destroy the WebGLGraphicsData - for (var id in this._webgl) { - for (var j = 0; j < this._webgl[id].data.length; ++j) { - this._webgl[id].data[j].destroy(); - } - } - - if(this._spriteRect) - { - this._spriteRect.destroy(); - } - this.graphicsData = null; - - this.currentPath = null; - this._webgl = null; - this._localBounds = null; -}; diff --git a/src/core/graphics/GraphicsData.js b/src/core/graphics/GraphicsData.js index 45c7cda..b2a6b70 100644 --- a/src/core/graphics/GraphicsData.js +++ b/src/core/graphics/GraphicsData.js @@ -11,95 +11,96 @@ * @param fill {boolean} whether or not the shape is filled with a colour * @param shape {PIXI.Circle|PIXI.Rectangle|PIXI.Ellipse|PIXI.Polygon} The shape object to draw. */ -function GraphicsData(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) -{ - /* - * @member {number} the width of the line to draw - */ - this.lineWidth = lineWidth; +class GraphicsData { + constructor(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) + { + /* + * @member {number} the width of the line to draw + */ + this.lineWidth = lineWidth; - /* - * @member {number} the color of the line to draw - */ - this.lineColor = lineColor; + /* + * @member {number} the color of the line to draw + */ + this.lineColor = lineColor; - /* - * @member {number} the alpha of the line to draw - */ - this.lineAlpha = lineAlpha; + /* + * @member {number} the alpha of the line to draw + */ + this.lineAlpha = lineAlpha; - /* - * @member {number} cached tint of the line to draw - */ - this._lineTint = lineColor; + /* + * @member {number} cached tint of the line to draw + */ + this._lineTint = lineColor; - /* - * @member {number} the color of the fill - */ - this.fillColor = fillColor; + /* + * @member {number} the color of the fill + */ + this.fillColor = fillColor; - /* - * @member {number} the alpha of the fill - */ - this.fillAlpha = fillAlpha; + /* + * @member {number} the alpha of the fill + */ + this.fillAlpha = fillAlpha; - /* - * @member {number} cached tint of the fill - */ - this._fillTint = fillColor; + /* + * @member {number} cached tint of the fill + */ + this._fillTint = fillColor; - /* - * @member {boolean} whether or not the shape is filled with a colour - */ - this.fill = fill; + /* + * @member {boolean} whether or not the shape is filled with a colour + */ + this.fill = fill; - this.holes = []; + this.holes = []; - /* - * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. - */ - this.shape = shape; + /* + * @member {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} The shape object to draw. + */ + this.shape = shape; - /* - * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, - */ - this.type = shape.type; + /* + * @member {number} The type of the shape, see the Const.Shapes file for all the existing types, + */ + this.type = shape.type; + } + + /** + * Creates a new GraphicsData object with the same values as this one. + * + * @return {PIXI.GraphicsData} Cloned GraphicsData object + */ + clone() + { + return new GraphicsData( + this.lineWidth, + this.lineColor, + this.lineAlpha, + this.fillColor, + this.fillAlpha, + this.fill, + this.shape + ); + } + + /** + * + * + */ + addHole(shape) + { + this.holes.push(shape); + } + + /** + * Destroys the Graphics data. + */ + destroy() { + this.shape = null; + this.holes = null; + } } -GraphicsData.prototype.constructor = GraphicsData; -module.exports = GraphicsData; - -/** - * Creates a new GraphicsData object with the same values as this one. - * - * @return {PIXI.GraphicsData} Cloned GraphicsData object - */ -GraphicsData.prototype.clone = function () -{ - return new GraphicsData( - this.lineWidth, - this.lineColor, - this.lineAlpha, - this.fillColor, - this.fillAlpha, - this.fill, - this.shape - ); -}; - -/** - * - * - */ -GraphicsData.prototype.addHole = function (shape) -{ - this.holes.push(shape); -}; - -/** - * Destroys the Graphics data. - */ -GraphicsData.prototype.destroy = function () { - this.shape = null; - this.holes = null; -}; +module.exports = GraphicsData; \ No newline at end of file diff --git a/src/core/graphics/canvas/CanvasGraphicsRenderer.js b/src/core/graphics/canvas/CanvasGraphicsRenderer.js index 88cb020..f41190e 100644 --- a/src/core/graphics/canvas/CanvasGraphicsRenderer.js +++ b/src/core/graphics/canvas/CanvasGraphicsRenderer.js @@ -21,256 +21,257 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.SystemRenderer} The current PIXI renderer. */ -function CanvasGraphicsRenderer(renderer) -{ - this.renderer = renderer; +class CanvasGraphicsRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /* + * Renders a Graphics object to a canvas. + * + * @param graphics {PIXI.Graphics} the actual graphics object to render + * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas + */ + render(graphics) + { + var renderer = this.renderer; + var context = renderer.context; + var worldAlpha = graphics.worldAlpha; + var transform = graphics.transform.worldTransform; + var resolution = renderer.resolution; + + // if the tint has changed, set the graphics object to dirty. + if (this._prevTint !== this.tint) { + this.dirty = true; + } + + context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + + if (graphics.dirty) + { + this.updateGraphicsTint(graphics); + graphics.dirty = false; + } + + renderer.setBlendMode(graphics.blendMode); + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + var fillColor = data._fillTint; + var lineColor = data._lineTint; + + context.lineWidth = data.lineWidth; + + if (data.type === CONST.SHAPES.POLY) + { + context.beginPath(); + + this.renderPolygon(shape.points, shape.closed, context); + + for (var j = 0; j < data.holes.length; j++) + { + var hole = data.holes[j]; + this.renderPolygon(hole.points, true, context); + } + + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RECT) + { + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fillRect(shape.x, shape.y, shape.width, shape.height); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.strokeRect(shape.x, shape.y, shape.width, shape.height); + } + } + 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(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + 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; + var h = shape.height * 2; + + var x = shape.x - w/2; + var y = shape.y - h/2; + + context.beginPath(); + + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle + + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + + context.closePath(); + + if (data.fill) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + else if (data.type === CONST.SHAPES.RREC) + { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; + + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; + + context.beginPath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + + if (data.fillColor || data.fillColor === 0) + { + context.globalAlpha = data.fillAlpha * worldAlpha; + context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); + context.fill(); + + } + if (data.lineWidth) + { + context.globalAlpha = data.lineAlpha * worldAlpha; + context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); + context.stroke(); + } + } + } + } + + /* + * Updates the tint of a graphics object + * + * @private + * @param graphics {PIXI.Graphics} the graphics that will have its tint updated + * + */ + updateGraphicsTint(graphics) + { + graphics._prevTint = graphics.tint; + + var tintR = (graphics.tint >> 16 & 0xFF) / 255; + var tintG = (graphics.tint >> 8 & 0xFF) / 255; + var tintB = (graphics.tint & 0xFF)/ 255; + + for (var i = 0; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + var fillColor = data.fillColor | 0; + var lineColor = data.lineColor | 0; + + // super inline cos im an optimization NAZI :) + data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); + data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); + } + } + + renderPolygon(points, close, context) + { + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + if (close) + { + context.closePath(); + } + } + + /* + * destroy graphics object + * + */ + destroy() + { + this.renderer = null; + } + } - -CanvasGraphicsRenderer.prototype.constructor = CanvasGraphicsRenderer; module.exports = CanvasGraphicsRenderer; CanvasRenderer.registerPlugin('graphics', CanvasGraphicsRenderer); - -/* - * Renders a Graphics object to a canvas. - * - * @param graphics {PIXI.Graphics} the actual graphics object to render - * @param context {CanvasRenderingContext2D} the 2d drawing method of the canvas - */ -CanvasGraphicsRenderer.prototype.render = function (graphics) -{ - var renderer = this.renderer; - var context = renderer.context; - var worldAlpha = graphics.worldAlpha; - var transform = graphics.transform.worldTransform; - var resolution = renderer.resolution; - - // if the tint has changed, set the graphics object to dirty. - if (this._prevTint !== this.tint) { - this.dirty = true; - } - - context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - - if (graphics.dirty) - { - this.updateGraphicsTint(graphics); - graphics.dirty = false; - } - - renderer.setBlendMode(graphics.blendMode); - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; - - var fillColor = data._fillTint; - var lineColor = data._lineTint; - - context.lineWidth = data.lineWidth; - - if (data.type === CONST.SHAPES.POLY) - { - context.beginPath(); - - this.renderPolygon(shape.points, shape.closed, context); - - for (var j = 0; j < data.holes.length; j++) - { - var hole = data.holes[j]; - this.renderPolygon(hole.points, true, context); - } - - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RECT) - { - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fillRect(shape.x, shape.y, shape.width, shape.height); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.strokeRect(shape.x, shape.y, shape.width, shape.height); - } - } - 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(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - 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; - var h = shape.height * 2; - - var x = shape.x - w/2; - var y = shape.y - h/2; - - context.beginPath(); - - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle - - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - - context.closePath(); - - if (data.fill) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - else if (data.type === CONST.SHAPES.RREC) - { - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; - - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.beginPath(); - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); - - if (data.fillColor || data.fillColor === 0) - { - context.globalAlpha = data.fillAlpha * worldAlpha; - context.fillStyle = '#' + ('00000' + ( fillColor | 0).toString(16)).substr(-6); - context.fill(); - - } - if (data.lineWidth) - { - context.globalAlpha = data.lineAlpha * worldAlpha; - context.strokeStyle = '#' + ('00000' + ( lineColor | 0).toString(16)).substr(-6); - context.stroke(); - } - } - } -}; - -/* - * Updates the tint of a graphics object - * - * @private - * @param graphics {PIXI.Graphics} the graphics that will have its tint updated - * - */ -CanvasGraphicsRenderer.prototype.updateGraphicsTint = function (graphics) -{ - graphics._prevTint = graphics.tint; - - var tintR = (graphics.tint >> 16 & 0xFF) / 255; - var tintG = (graphics.tint >> 8 & 0xFF) / 255; - var tintB = (graphics.tint & 0xFF)/ 255; - - for (var i = 0; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - var fillColor = data.fillColor | 0; - var lineColor = data.lineColor | 0; - - // super inline cos im an optimization NAZI :) - data._fillTint = (((fillColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((fillColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (fillColor & 0xFF) / 255 * tintB*255); - data._lineTint = (((lineColor >> 16 & 0xFF) / 255 * tintR*255 << 16) + ((lineColor >> 8 & 0xFF) / 255 * tintG*255 << 8) + (lineColor & 0xFF) / 255 * tintB*255); - } -}; - -CanvasGraphicsRenderer.prototype.renderPolygon = function (points, close, context) -{ - context.moveTo(points[0], points[1]); - - for (var j=1; j < points.length/2; j++) - { - context.lineTo(points[j * 2], points[j * 2 + 1]); - } - - if (close) - { - context.closePath(); - } -}; - -/* - * destroy graphics object - * - */ -CanvasGraphicsRenderer.prototype.destroy = function () -{ - this.renderer = null; -}; diff --git a/src/core/graphics/webgl/GraphicsRenderer.js b/src/core/graphics/webgl/GraphicsRenderer.js index ccf1117..19b441c 100644 --- a/src/core/graphics/webgl/GraphicsRenderer.js +++ b/src/core/graphics/webgl/GraphicsRenderer.js @@ -21,203 +21,204 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function GraphicsRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); +class GraphicsRenderer extends ObjectRenderer { + constructor(renderer) + { + super(renderer); - this.graphicsDataPool = []; + this.graphicsDataPool = []; - this.primitiveShader = null; + this.primitiveShader = null; - this.gl = renderer.gl; + this.gl = renderer.gl; - // easy access! - this.CONTEXT_UID = 0; + // easy access! + this.CONTEXT_UID = 0; + } + + /** + * Called when there is a WebGL context change + * + * @private + * + */ + onContextChange() + { + this.gl = this.renderer.gl; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; + this.primitiveShader = new PrimitiveShader(this.gl); + } + + /** + * Destroys this renderer. + * + */ + destroy() + { + ObjectRenderer.prototype.destroy.call(this); + + for (var i = 0; i < this.graphicsDataPool.length; ++i) { + this.graphicsDataPool[i].destroy(); + } + + this.graphicsDataPool = null; + } + + /** + * Renders a graphics object. + * + * @param graphics {PIXI.Graphics} The graphics object to render. + */ + render(graphics) + { + var renderer = this.renderer; + var gl = renderer.gl; + + var webGLData; + + var webGL = graphics._webGL[this.CONTEXT_UID]; + + if (!webGL || graphics.dirty !== webGL.dirty ) + { + + this.updateGraphics(graphics); + + webGL = graphics._webGL[this.CONTEXT_UID]; + } + + + + // This could be speeded up for sure! + var shader = this.primitiveShader; + renderer.bindShader(shader); + renderer.state.setBlendMode( graphics.blendMode ); + + for (var i = 0, n = webGL.data.length; i < n; i++) + { + webGLData = webGL.data[i]; + var shaderTemp = webGLData.shader; + + renderer.bindShader(shaderTemp); + shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); + shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); + shaderTemp.uniforms.alpha = graphics.worldAlpha; + + webGLData.vao.bind() + .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) + .unbind(); + } + } + + /** + * Updates the graphics object + * + * @private + * @param graphics {PIXI.Graphics} The graphics object to update + */ + updateGraphics(graphics) + { + var gl = this.renderer.gl; + + // get the contexts graphics object + var webGL = graphics._webGL[this.CONTEXT_UID]; + + // if the graphics object does not exist in the webGL context time to create it! + if (!webGL) + { + webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; + + } + + // flag the graphics as not dirty as we are about to update it... + webGL.dirty = graphics.dirty; + + var i; + + // if the user cleared the graphics object we will need to clear every object + if (graphics.clearDirty !== webGL.clearDirty) + { + webGL.clearDirty = graphics.clearDirty; + + // loop through and return all the webGLDatas to the object pool so than can be reused later on + for (i = 0; i < webGL.data.length; i++) + { + var graphicsData = webGL.data[i]; + this.graphicsDataPool.push( graphicsData ); + } + + // clear the array and reset the index.. + webGL.data = []; + webGL.lastIndex = 0; + } + + var webGLData; + + // loop through the graphics datas and construct each one.. + // if the object is a complex fill then the new stencil buffer technique will be used + // other wise graphics objects will be pushed into a batch.. + for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) + { + var data = graphics.graphicsData[i]; + + //TODO - this can be simplified + webGLData = this.getWebGLData(webGL, 0); + + if (data.type === CONST.SHAPES.POLY) + { + buildPoly(data, webGLData); + } + if (data.type === CONST.SHAPES.RECT) + { + buildRectangle(data, webGLData); + } + else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) + { + buildCircle(data, webGLData); + } + else if (data.type === CONST.SHAPES.RREC) + { + buildRoundedRectangle(data, webGLData); + } + + webGL.lastIndex++; + } + + // upload all the dirty data... + for (i = 0; i < webGL.data.length; i++) + { + webGLData = webGL.data[i]; + + if (webGLData.dirty) + { + webGLData.upload(); + } + } + } + + /** + * + * @private + * @param webGL {WebGLRenderingContext} the current WebGL drawing context + * @param type {number} TODO @Alvin + */ + getWebGLData(webGL, type) + { + var webGLData = webGL.data[webGL.data.length-1]; + + if (!webGLData || webGLData.points.length > 320000) + { + webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); + webGLData.reset(type); + webGL.data.push(webGLData); + } + + webGLData.dirty = true; + + return webGLData; + } + } -GraphicsRenderer.prototype = Object.create(ObjectRenderer.prototype); -GraphicsRenderer.prototype.constructor = GraphicsRenderer; module.exports = GraphicsRenderer; WebGLRenderer.registerPlugin('graphics', GraphicsRenderer); - -/** - * Called when there is a WebGL context change - * - * @private - * - */ -GraphicsRenderer.prototype.onContextChange = function() -{ - this.gl = this.renderer.gl; - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.primitiveShader = new PrimitiveShader(this.gl); -}; - -/** - * Destroys this renderer. - * - */ -GraphicsRenderer.prototype.destroy = function () -{ - ObjectRenderer.prototype.destroy.call(this); - - for (var i = 0; i < this.graphicsDataPool.length; ++i) { - this.graphicsDataPool[i].destroy(); - } - - this.graphicsDataPool = null; -}; - -/** - * Renders a graphics object. - * - * @param graphics {PIXI.Graphics} The graphics object to render. - */ -GraphicsRenderer.prototype.render = function(graphics) -{ - var renderer = this.renderer; - var gl = renderer.gl; - - var webGLData; - - var webGL = graphics._webGL[this.CONTEXT_UID]; - - if (!webGL || graphics.dirty !== webGL.dirty ) - { - - this.updateGraphics(graphics); - - webGL = graphics._webGL[this.CONTEXT_UID]; - } - - - - // This could be speeded up for sure! - var shader = this.primitiveShader; - renderer.bindShader(shader); - renderer.state.setBlendMode( graphics.blendMode ); - - for (var i = 0, n = webGL.data.length; i < n; i++) - { - webGLData = webGL.data[i]; - var shaderTemp = webGLData.shader; - - renderer.bindShader(shaderTemp); - shaderTemp.uniforms.translationMatrix = graphics.transform.worldTransform.toArray(true); - shaderTemp.uniforms.tint = utils.hex2rgb(graphics.tint); - shaderTemp.uniforms.alpha = graphics.worldAlpha; - - webGLData.vao.bind() - .draw(gl.TRIANGLE_STRIP, webGLData.indices.length) - .unbind(); - } -}; - -/** - * Updates the graphics object - * - * @private - * @param graphics {PIXI.Graphics} The graphics object to update - */ -GraphicsRenderer.prototype.updateGraphics = function(graphics) -{ - var gl = this.renderer.gl; - - // get the contexts graphics object - var webGL = graphics._webGL[this.CONTEXT_UID]; - - // if the graphics object does not exist in the webGL context time to create it! - if (!webGL) - { - webGL = graphics._webGL[this.CONTEXT_UID] = {lastIndex:0, data:[], gl:gl, clearDirty:-1, dirty:-1}; - - } - - // flag the graphics as not dirty as we are about to update it... - webGL.dirty = graphics.dirty; - - var i; - - // if the user cleared the graphics object we will need to clear every object - if (graphics.clearDirty !== webGL.clearDirty) - { - webGL.clearDirty = graphics.clearDirty; - - // loop through and return all the webGLDatas to the object pool so than can be reused later on - for (i = 0; i < webGL.data.length; i++) - { - var graphicsData = webGL.data[i]; - this.graphicsDataPool.push( graphicsData ); - } - - // clear the array and reset the index.. - webGL.data = []; - webGL.lastIndex = 0; - } - - var webGLData; - - // loop through the graphics datas and construct each one.. - // if the object is a complex fill then the new stencil buffer technique will be used - // other wise graphics objects will be pushed into a batch.. - for (i = webGL.lastIndex; i < graphics.graphicsData.length; i++) - { - var data = graphics.graphicsData[i]; - - //TODO - this can be simplified - webGLData = this.getWebGLData(webGL, 0); - - if (data.type === CONST.SHAPES.POLY) - { - buildPoly(data, webGLData); - } - if (data.type === CONST.SHAPES.RECT) - { - buildRectangle(data, webGLData); - } - else if (data.type === CONST.SHAPES.CIRC || data.type === CONST.SHAPES.ELIP) - { - buildCircle(data, webGLData); - } - else if (data.type === CONST.SHAPES.RREC) - { - buildRoundedRectangle(data, webGLData); - } - - webGL.lastIndex++; - } - - // upload all the dirty data... - for (i = 0; i < webGL.data.length; i++) - { - webGLData = webGL.data[i]; - - if (webGLData.dirty) - { - webGLData.upload(); - } - } -}; - -/** - * - * @private - * @param webGL {WebGLRenderingContext} the current WebGL drawing context - * @param type {number} TODO @Alvin - */ -GraphicsRenderer.prototype.getWebGLData = function (webGL, type) -{ - var webGLData = webGL.data[webGL.data.length-1]; - - if (!webGLData || webGLData.points.length > 320000) - { - webGLData = this.graphicsDataPool.pop() || new WebGLGraphicsData(this.renderer.gl, this.primitiveShader, this.renderer.state.attribsState); - webGLData.reset(type); - webGL.data.push(webGLData); - } - - webGLData.dirty = true; - - return webGLData; -}; diff --git a/src/core/graphics/webgl/WebGLGraphicsData.js b/src/core/graphics/webgl/WebGLGraphicsData.js index 4eb36c8..74af989 100644 --- a/src/core/graphics/webgl/WebGLGraphicsData.js +++ b/src/core/graphics/webgl/WebGLGraphicsData.js @@ -11,115 +11,115 @@ * @param shader {PIXI.Shader} The shader * @param attribsState {object} The state for the VAO */ -function WebGLGraphicsData(gl, shader, attribsState) -{ +class WebGLGraphicsData { + constructor(gl, shader, attribsState) + { + + /** + * The current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + //TODO does this need to be split before uploding?? + /** + * An array of color components (r,g,b) + * @member {number[]} + */ + this.color = [0,0,0]; // color split! + + /** + * An array of points to draw + * @member {PIXI.Point[]} + */ + this.points = []; + + /** + * The indices of the vertices + * @member {number[]} + */ + this.indices = []; + /** + * The main buffer + * @member {WebGLBuffer} + */ + this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + + /** + * The index buffer + * @member {WebGLBuffer} + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + + /** + * Whether this graphics is dirty or not + * @member {boolean} + */ + this.dirty = true; + + this.glPoints = null; + this.glIndices = null; + + /** + * + * @member {PIXI.Shader} + */ + this.shader = shader; + + this.vao = new glCore.VertexArrayObject(gl, attribsState) + .addIndex(this.indexBuffer) + .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) + .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); + + } /** - * The current WebGL drawing context - * - * @member {WebGLRenderingContext} + * Resets the vertices and the indices */ - this.gl = gl; - - //TODO does this need to be split before uploding?? - /** - * An array of color components (r,g,b) - * @member {number[]} - */ - this.color = [0,0,0]; // color split! + reset() + { + this.points.length = 0; + this.indices.length = 0; + } /** - * An array of points to draw - * @member {PIXI.Point[]} + * Binds the buffers and uploads the data */ - this.points = []; + upload() + { + this.glPoints = new Float32Array(this.points); + this.buffer.upload( this.glPoints ); + + this.glIndices = new Uint16Array(this.indices); + this.indexBuffer.upload( this.glIndices ); + + this.dirty = false; + } + /** - * The indices of the vertices - * @member {number[]} + * Empties all the data */ - this.indices = []; - /** - * The main buffer - * @member {WebGLBuffer} - */ - this.buffer = glCore.GLBuffer.createVertexBuffer(gl); + destroy() + { + this.color = null; + this.points = null; + this.indices = null; - /** - * The index buffer - * @member {WebGLBuffer} - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl); + this.vao.destroy(); + this.buffer.destroy(); + this.indexBuffer.destroy(); - /** - * Whether this graphics is dirty or not - * @member {boolean} - */ - this.dirty = true; + this.gl = null; - this.glPoints = null; - this.glIndices = null; + this.buffer = null; + this.indexBuffer = null; - /** - * - * @member {PIXI.Shader} - */ - this.shader = shader; - - this.vao = new glCore.VertexArrayObject(gl, attribsState) - .addIndex(this.indexBuffer) - .addAttribute(this.buffer, shader.attributes.aVertexPosition, gl.FLOAT, false, 4 * 6, 0) - .addAttribute(this.buffer, shader.attributes.aColor, gl.FLOAT, false, 4 * 6, 2 * 4); - + this.glPoints = null; + this.glIndices = null; + } } -WebGLGraphicsData.prototype.constructor = WebGLGraphicsData; module.exports = WebGLGraphicsData; - -/** - * Resets the vertices and the indices - */ -WebGLGraphicsData.prototype.reset = function () -{ - this.points.length = 0; - this.indices.length = 0; -}; - -/** - * Binds the buffers and uploads the data - */ -WebGLGraphicsData.prototype.upload = function () -{ - this.glPoints = new Float32Array(this.points); - this.buffer.upload( this.glPoints ); - - this.glIndices = new Uint16Array(this.indices); - this.indexBuffer.upload( this.glIndices ); - - this.dirty = false; -}; - - - -/** - * Empties all the data - */ -WebGLGraphicsData.prototype.destroy = function () -{ - this.color = null; - this.points = null; - this.indices = null; - - this.vao.destroy(); - this.buffer.destroy(); - this.indexBuffer.destroy(); - - this.gl = null; - - this.buffer = null; - this.indexBuffer = null; - - this.glPoints = null; - this.glIndices = null; -}; diff --git a/src/core/graphics/webgl/shaders/PrimitiveShader.js b/src/core/graphics/webgl/shaders/PrimitiveShader.js index 76634ae..367ac35 100644 --- a/src/core/graphics/webgl/shaders/PrimitiveShader.js +++ b/src/core/graphics/webgl/shaders/PrimitiveShader.js @@ -8,40 +8,38 @@ * @extends PIXI.Shader * @param gl {WebGLRenderingContext} The webgl shader manager this shader works for. */ -function PrimitiveShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec4 aColor;', +class PrimitiveShader extends Shader { + constructor(gl) + { + super(gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec4 aColor;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'varying vec4 vColor;', + 'varying vec4 vColor;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vColor = aColor * vec4(tint * alpha, alpha);', - '}' - ].join('\n'), - // fragment shader - [ - 'varying vec4 vColor;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vColor = aColor * vec4(tint * alpha, alpha);', + '}' + ].join('\n'), + // fragment shader + [ + 'varying vec4 vColor;', - 'void main(void){', - ' gl_FragColor = vColor;', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = vColor;', + '}' + ].join('\n') + ); + } } -PrimitiveShader.prototype = Object.create(Shader.prototype); -PrimitiveShader.prototype.constructor = PrimitiveShader; - module.exports = PrimitiveShader; diff --git a/src/core/math/Matrix.js b/src/core/math/Matrix.js index 4b2b345..fb110a6 100644 --- a/src/core/math/Matrix.js +++ b/src/core/math/Matrix.js @@ -14,475 +14,480 @@ * @class * @memberof PIXI */ -function Matrix() -{ - /** - * @member {number} - * @default 1 - */ - this.a = 1; +class Matrix { + constructor() + { + /** + * @member {number} + * @default 1 + */ + this.a = 1; + + /** + * @member {number} + * @default 0 + */ + this.b = 0; + + /** + * @member {number} + * @default 0 + */ + this.c = 0; + + /** + * @member {number} + * @default 1 + */ + this.d = 1; + + /** + * @member {number} + * @default 0 + */ + this.tx = 0; + + /** + * @member {number} + * @default 0 + */ + this.ty = 0; + + this.array = null; + } + + /** + * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: + * + * a = array[0] + * b = array[1] + * c = array[3] + * d = array[4] + * tx = array[2] + * ty = array[5] + * + * @param array {number[]} The array that the matrix will be populated from. + */ + fromArray(array) + { + this.a = array[0]; + this.b = array[1]; + this.c = array[3]; + this.d = array[4]; + this.tx = array[2]; + this.ty = array[5]; + } + + + /** + * sets the matrix properties + * + * @param {number} a + * @param {number} b + * @param {number} c + * @param {number} d + * @param {number} tx + * @param {number} ty + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + set(a, b, c, d, tx, ty) + { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.tx = tx; + this.ty = ty; + + return this; + } + + + /** + * Creates an array from the current Matrix object. + * + * @param transpose {boolean} Whether we need to transpose the matrix or not + * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out + * @return {number[]} the newly created array which contains the matrix + */ + toArray(transpose, out) + { + if (!this.array) + { + this.array = new Float32Array(9); + } + + var array = out || this.array; + + if (transpose) + { + array[0] = this.a; + array[1] = this.b; + array[2] = 0; + array[3] = this.c; + array[4] = this.d; + array[5] = 0; + array[6] = this.tx; + array[7] = this.ty; + array[8] = 1; + } + else + { + array[0] = this.a; + array[1] = this.c; + array[2] = this.tx; + array[3] = this.b; + array[4] = this.d; + array[5] = this.ty; + array[6] = 0; + array[7] = 0; + array[8] = 1; + } + + return array; + } + + /** + * Get a new position with the current transformation applied. + * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, transformed through this matrix + */ + apply(pos, newPos) + { + newPos = newPos || new Point(); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.a * x + this.c * y + this.tx; + newPos.y = this.b * x + this.d * y + this.ty; + + return newPos; + } + + /** + * Get a new position with the inverse of the current transformation applied. + * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) + * + * @param pos {PIXI.Point} The origin + * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) + * @return {PIXI.Point} The new point, inverse-transformed through this matrix + */ + applyInverse(pos, newPos) + { + newPos = newPos || new Point(); + + var id = 1 / (this.a * this.d + this.c * -this.b); + + var x = pos.x; + var y = pos.y; + + newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; + newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; + + return newPos; + } + + /** + * Translates the matrix on the x and y. + * + * @param {number} x How much to translate x by + * @param {number} y How much to translate y by + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + translate(x, y) + { + this.tx += x; + this.ty += y; + + return this; + } + + /** + * Applies a scale transformation to the matrix. + * + * @param {number} x The amount to scale horizontally + * @param {number} y The amount to scale vertically + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + scale(x, y) + { + this.a *= x; + this.d *= y; + this.c *= x; + this.b *= y; + this.tx *= x; + this.ty *= y; + + return this; + } + + + /** + * Applies a rotation transformation to the matrix. + * + * @param {number} angle - The angle in radians. + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + rotate(angle) + { + var cos = Math.cos( angle ); + var sin = Math.sin( angle ); + + var a1 = this.a; + var c1 = this.c; + var tx1 = this.tx; + + this.a = a1 * cos-this.b * sin; + this.b = a1 * sin+this.b * cos; + this.c = c1 * cos-this.d * sin; + this.d = c1 * sin+this.d * cos; + this.tx = tx1 * cos - this.ty * sin; + this.ty = tx1 * sin + this.ty * cos; + + return this; + } + + /** + * Appends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + append(matrix) + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + + this.a = matrix.a * a1 + matrix.b * c1; + this.b = matrix.a * b1 + matrix.b * d1; + this.c = matrix.c * a1 + matrix.d * c1; + this.d = matrix.c * b1 + matrix.d * d1; + + this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; + this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; + + return this; + } + + /** + * Sets the matrix based on all the available properties + * + * @param {number} x Position on the x axis + * @param {number} y Position on the y axis + * @param {number} pivotX Pivot on the x axis + * @param {number} pivotY Pivot on the y axis + * @param {number} scaleX Scale on the x axis + * @param {number} scaleY Scale on the y axis + * @param {number} rotation Rotation in radians + * @param {number} skewX Skew on the x axis + * @param {number} skewY Skew on the y axis + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + setTransform(x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) + { + var a, b, c, d, sr, cr, cy, sy, nsx, cx; + + sr = Math.sin(rotation); + cr = Math.cos(rotation); + cy = Math.cos(skewY); + sy = Math.sin(skewY); + nsx = -Math.sin(skewX); + cx = Math.cos(skewX); + + a = cr * scaleX; + b = sr * scaleX; + c = -sr * scaleY; + d = cr * scaleY; + + this.a = cy * a + sy * c; + this.b = cy * b + sy * d; + this.c = nsx * a + cx * c; + this.d = nsx * b + cx * d; + + this.tx = x + ( pivotX * a + pivotY * c ); + this.ty = y + ( pivotX * b + pivotY * d ); + + return this; + } + + /** + * Prepends the given Matrix to this Matrix. + * + * @param {PIXI.Matrix} matrix + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + prepend(matrix) + { + var tx1 = this.tx; + + if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) + { + var a1 = this.a; + var c1 = this.c; + this.a = a1*matrix.a+this.b*matrix.c; + this.b = a1*matrix.b+this.b*matrix.d; + this.c = c1*matrix.a+this.d*matrix.c; + this.d = c1*matrix.b+this.d*matrix.d; + } + + this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; + this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; + + return this; + } + + /** + * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. + * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. + * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies + */ + decompose(transform) + { + // sort out rotation / skew.. + var a = this.a, + b = this.b, + c = this.c, + d = this.d; + + var skewX = Math.atan2(-c, d); + var skewY = Math.atan2(b, a); + + var delta = Math.abs(1-skewX/skewY); + + if (delta < 0.00001) + { + transform.rotation = skewY; + + if (a < 0 && d >= 0) + { + transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; + } + + transform.skew.x = transform.skew.y = 0; + + } + else + { + transform.skew.x = skewX; + transform.skew.y = skewY; + } + + // next set scale + transform.scale.x = Math.sqrt(a * a + b * b); + transform.scale.y = Math.sqrt(c * c + d * d); + + // next set position + transform.position.x = this.tx; + transform.position.y = this.ty; + + return transform; + } + + + /** + * Inverts this matrix + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + invert() + { + var a1 = this.a; + var b1 = this.b; + var c1 = this.c; + var d1 = this.d; + var tx1 = this.tx; + var n = a1*d1-b1*c1; + + this.a = d1/n; + this.b = -b1/n; + this.c = -c1/n; + this.d = a1/n; + this.tx = (c1*this.ty-d1*tx1)/n; + this.ty = -(a1*this.ty-b1*tx1)/n; + + return this; + } + + + /** + * Resets this Matix to an identity (default) matrix. + * + * @return {PIXI.Matrix} This matrix. Good for chaining method calls. + */ + identity() + { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.tx = 0; + this.ty = 0; + + return this; + } /** - * @member {number} - * @default 0 + * Creates a new Matrix object with the same values as this one. + * + * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. */ - this.b = 0; + clone() + { + var matrix = new Matrix(); + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 0 + * Changes the values of the given matrix to be the same as the ones in this matrix + * + * @return {PIXI.Matrix} The matrix given in parameter with its values updated. */ - this.c = 0; + copy(matrix) + { + matrix.a = this.a; + matrix.b = this.b; + matrix.c = this.c; + matrix.d = this.d; + matrix.tx = this.tx; + matrix.ty = this.ty; + + return matrix; + } /** - * @member {number} - * @default 1 + * A default (identity) matrix + * + * @static + * @const */ - this.d = 1; + static get IDENTITY() { + return new Matrix(); + } - /** - * @member {number} - * @default 0 - */ - this.tx = 0; - - /** - * @member {number} - * @default 0 - */ - this.ty = 0; - - this.array = null; + /** + * A temp matrix + * + * @static + * @const + */ + static get TEMP_MATRIX() { + return new Matrix(); + } } -Matrix.prototype.constructor = Matrix; -module.exports = Matrix; - -/** - * Creates a Matrix object based on the given array. The Element to Matrix mapping order is as follows: - * - * a = array[0] - * b = array[1] - * c = array[3] - * d = array[4] - * tx = array[2] - * ty = array[5] - * - * @param array {number[]} The array that the matrix will be populated from. - */ -Matrix.prototype.fromArray = function (array) -{ - this.a = array[0]; - this.b = array[1]; - this.c = array[3]; - this.d = array[4]; - this.tx = array[2]; - this.ty = array[5]; -}; - - -/** - * sets the matrix properties - * - * @param {number} a - * @param {number} b - * @param {number} c - * @param {number} d - * @param {number} tx - * @param {number} ty - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.set = function (a, b, c, d, tx, ty) -{ - this.a = a; - this.b = b; - this.c = c; - this.d = d; - this.tx = tx; - this.ty = ty; - - return this; -}; - - -/** - * Creates an array from the current Matrix object. - * - * @param transpose {boolean} Whether we need to transpose the matrix or not - * @param [out=new Float32Array(9)] {Float32Array} If provided the array will be assigned to out - * @return {number[]} the newly created array which contains the matrix - */ -Matrix.prototype.toArray = function (transpose, out) -{ - if (!this.array) - { - this.array = new Float32Array(9); - } - - var array = out || this.array; - - if (transpose) - { - array[0] = this.a; - array[1] = this.b; - array[2] = 0; - array[3] = this.c; - array[4] = this.d; - array[5] = 0; - array[6] = this.tx; - array[7] = this.ty; - array[8] = 1; - } - else - { - array[0] = this.a; - array[1] = this.c; - array[2] = this.tx; - array[3] = this.b; - array[4] = this.d; - array[5] = this.ty; - array[6] = 0; - array[7] = 0; - array[8] = 1; - } - - return array; -}; - -/** - * Get a new position with the current transformation applied. - * Can be used to go from a child's coordinate space to the world coordinate space. (e.g. rendering) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, transformed through this matrix - */ -Matrix.prototype.apply = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.a * x + this.c * y + this.tx; - newPos.y = this.b * x + this.d * y + this.ty; - - return newPos; -}; - -/** - * Get a new position with the inverse of the current transformation applied. - * Can be used to go from the world coordinate space to a child's coordinate space. (e.g. input) - * - * @param pos {PIXI.Point} The origin - * @param [newPos] {PIXI.Point} The point that the new position is assigned to (allowed to be same as input) - * @return {PIXI.Point} The new point, inverse-transformed through this matrix - */ -Matrix.prototype.applyInverse = function (pos, newPos) -{ - newPos = newPos || new Point(); - - var id = 1 / (this.a * this.d + this.c * -this.b); - - var x = pos.x; - var y = pos.y; - - newPos.x = this.d * id * x + -this.c * id * y + (this.ty * this.c - this.tx * this.d) * id; - newPos.y = this.a * id * y + -this.b * id * x + (-this.ty * this.a + this.tx * this.b) * id; - - return newPos; -}; - -/** - * Translates the matrix on the x and y. - * - * @param {number} x How much to translate x by - * @param {number} y How much to translate y by - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.translate = function (x, y) -{ - this.tx += x; - this.ty += y; - - return this; -}; - -/** - * Applies a scale transformation to the matrix. - * - * @param {number} x The amount to scale horizontally - * @param {number} y The amount to scale vertically - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.scale = function (x, y) -{ - this.a *= x; - this.d *= y; - this.c *= x; - this.b *= y; - this.tx *= x; - this.ty *= y; - - return this; -}; - - -/** - * Applies a rotation transformation to the matrix. - * - * @param {number} angle - The angle in radians. - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.rotate = function (angle) -{ - var cos = Math.cos( angle ); - var sin = Math.sin( angle ); - - var a1 = this.a; - var c1 = this.c; - var tx1 = this.tx; - - this.a = a1 * cos-this.b * sin; - this.b = a1 * sin+this.b * cos; - this.c = c1 * cos-this.d * sin; - this.d = c1 * sin+this.d * cos; - this.tx = tx1 * cos - this.ty * sin; - this.ty = tx1 * sin + this.ty * cos; - - return this; -}; - -/** - * Appends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.append = function (matrix) -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - - this.a = matrix.a * a1 + matrix.b * c1; - this.b = matrix.a * b1 + matrix.b * d1; - this.c = matrix.c * a1 + matrix.d * c1; - this.d = matrix.c * b1 + matrix.d * d1; - - this.tx = matrix.tx * a1 + matrix.ty * c1 + this.tx; - this.ty = matrix.tx * b1 + matrix.ty * d1 + this.ty; - - return this; -}; - -/** - * Sets the matrix based on all the available properties - * - * @param {number} x Position on the x axis - * @param {number} y Position on the y axis - * @param {number} pivotX Pivot on the x axis - * @param {number} pivotY Pivot on the y axis - * @param {number} scaleX Scale on the x axis - * @param {number} scaleY Scale on the y axis - * @param {number} rotation Rotation in radians - * @param {number} skewX Skew on the x axis - * @param {number} skewY Skew on the y axis - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.setTransform = function (x, y, pivotX, pivotY, scaleX, scaleY, rotation, skewX, skewY) -{ - var a, b, c, d, sr, cr, cy, sy, nsx, cx; - - sr = Math.sin(rotation); - cr = Math.cos(rotation); - cy = Math.cos(skewY); - sy = Math.sin(skewY); - nsx = -Math.sin(skewX); - cx = Math.cos(skewX); - - a = cr * scaleX; - b = sr * scaleX; - c = -sr * scaleY; - d = cr * scaleY; - - this.a = cy * a + sy * c; - this.b = cy * b + sy * d; - this.c = nsx * a + cx * c; - this.d = nsx * b + cx * d; - - this.tx = x + ( pivotX * a + pivotY * c ); - this.ty = y + ( pivotX * b + pivotY * d ); - - return this; -}; - -/** - * Prepends the given Matrix to this Matrix. - * - * @param {PIXI.Matrix} matrix - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.prepend = function(matrix) -{ - var tx1 = this.tx; - - if (matrix.a !== 1 || matrix.b !== 0 || matrix.c !== 0 || matrix.d !== 1) - { - var a1 = this.a; - var c1 = this.c; - this.a = a1*matrix.a+this.b*matrix.c; - this.b = a1*matrix.b+this.b*matrix.d; - this.c = c1*matrix.a+this.d*matrix.c; - this.d = c1*matrix.b+this.d*matrix.d; - } - - this.tx = tx1*matrix.a+this.ty*matrix.c+matrix.tx; - this.ty = tx1*matrix.b+this.ty*matrix.d+matrix.ty; - - return this; -}; - -/** - * Decomposes the matrix (x, y, scaleX, scaleY, and rotation) and sets the properties on to a transform. - * @param transform {PIXI.Transform|PIXI.TransformStatic} the transform to apply the properties to. - * @return {PIXI.Transform|PIXI.TransformStatic} The transform with the newly applied properies -*/ -Matrix.prototype.decompose = function(transform) -{ - // sort out rotation / skew.. - var a = this.a, - b = this.b, - c = this.c, - d = this.d; - - var skewX = Math.atan2(-c, d); - var skewY = Math.atan2(b, a); - - var delta = Math.abs(1-skewX/skewY); - - if (delta < 0.00001) - { - transform.rotation = skewY; - - if (a < 0 && d >= 0) - { - transform.rotation += (transform.rotation <= 0) ? Math.PI : -Math.PI; - } - - transform.skew.x = transform.skew.y = 0; - - } - else - { - transform.skew.x = skewX; - transform.skew.y = skewY; - } - - // next set scale - transform.scale.x = Math.sqrt(a * a + b * b); - transform.scale.y = Math.sqrt(c * c + d * d); - - // next set position - transform.position.x = this.tx; - transform.position.y = this.ty; - - return transform; -}; - - -/** - * Inverts this matrix - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.invert = function() -{ - var a1 = this.a; - var b1 = this.b; - var c1 = this.c; - var d1 = this.d; - var tx1 = this.tx; - var n = a1*d1-b1*c1; - - this.a = d1/n; - this.b = -b1/n; - this.c = -c1/n; - this.d = a1/n; - this.tx = (c1*this.ty-d1*tx1)/n; - this.ty = -(a1*this.ty-b1*tx1)/n; - - return this; -}; - - -/** - * Resets this Matix to an identity (default) matrix. - * - * @return {PIXI.Matrix} This matrix. Good for chaining method calls. - */ -Matrix.prototype.identity = function () -{ - this.a = 1; - this.b = 0; - this.c = 0; - this.d = 1; - this.tx = 0; - this.ty = 0; - - return this; -}; - -/** - * Creates a new Matrix object with the same values as this one. - * - * @return {PIXI.Matrix} A copy of this matrix. Good for chaining method calls. - */ -Matrix.prototype.clone = function () -{ - var matrix = new Matrix(); - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * Changes the values of the given matrix to be the same as the ones in this matrix - * - * @return {PIXI.Matrix} The matrix given in parameter with its values updated. - */ -Matrix.prototype.copy = function (matrix) -{ - matrix.a = this.a; - matrix.b = this.b; - matrix.c = this.c; - matrix.d = this.d; - matrix.tx = this.tx; - matrix.ty = this.ty; - - return matrix; -}; - -/** - * A default (identity) matrix - * - * @static - * @const - */ -Matrix.IDENTITY = new Matrix(); - -/** - * A temp matrix - * - * @static - * @const - */ -Matrix.TEMP_MATRIX = new Matrix(); +module.exports = Matrix; \ No newline at end of file diff --git a/src/core/math/ObservablePoint.js b/src/core/math/ObservablePoint.js index 1dc1c27..615b284 100644 --- a/src/core/math/ObservablePoint.js +++ b/src/core/math/ObservablePoint.js @@ -10,20 +10,54 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function ObservablePoint(cb, scope, x, y) -{ - this._x = x || 0; - this._y = y || 0; +class ObservablePoint { + constructor(cb, scope, x, y) + { + this._x = x || 0; + this._y = y || 0; - this.cb = cb; - this.scope = scope; + this.cb = cb; + this.scope = scope; + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + var _x = x || 0; + var _y = y || ( (y !== 0) ? _x : 0 ); + if (this._x !== _x || this._y !== _y) + { + this._x = _x; + this._y = _y; + this.cb.call(this.scope); + } + } + + /** + * Copies the data from another point + * + * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from + */ + copy(point) + { + if (this._x !== point.x || this._y !== point.y) + { + this._x = point.x; + this._y = point.y; + this.cb.call(this.scope); + } + } } -ObservablePoint.prototype.constructor = ObservablePoint; module.exports = ObservablePoint; - Object.defineProperties(ObservablePoint.prototype, { /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. @@ -64,37 +98,3 @@ } } }); - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -ObservablePoint.prototype.set = function (x, y) -{ - var _x = x || 0; - var _y = y || ( (y !== 0) ? _x : 0 ); - if (this._x !== _x || this._y !== _y) - { - this._x = _x; - this._y = _y; - this.cb.call(this.scope); - } -}; - -/** - * Copies the data from another point - * - * @param point {PIXI.Point|PIXI.ObservablePoint} point to copy from - */ -ObservablePoint.prototype.copy = function (point) -{ - if (this._x !== point.x || this._y !== point.y) - { - this._x = point.x; - this._y = point.y; - this.cb.call(this.scope); - } -}; diff --git a/src/core/math/Point.js b/src/core/math/Point.js index 9e1da91..dc5c36d 100644 --- a/src/core/math/Point.js +++ b/src/core/math/Point.js @@ -7,62 +7,64 @@ * @param [x=0] {number} position of the point on the x axis * @param [y=0] {number} position of the point on the y axis */ -function Point(x, y) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; +class Point { + constructor(x, y) + { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + } /** - * @member {number} - * @default 0 + * Creates a clone of this point + * + * @return {PIXI.Point} a copy of the point */ - this.y = y || 0; + clone() + { + return new Point(this.x, this.y); + } + + /** + * Copies x and y from the given point + * + * @param p {PIXI.Point} + */ + copy(p) { + this.set(p.x, p.y); + } + + /** + * Returns true if the given point is equal to this point + * + * @param p {PIXI.Point} + * @returns {boolean} Whether the given point equal to this point + */ + equals(p) { + return (p.x === this.x) && (p.y === this.y); + } + + /** + * Sets the point to a new x and y position. + * If y is omitted, both x and y will be set to x. + * + * @param [x=0] {number} position of the point on the x axis + * @param [y=0] {number} position of the point on the y axis + */ + set(x, y) + { + this.x = x || 0; + this.y = y || ( (y !== 0) ? this.x : 0 ) ; + } + } -Point.prototype.constructor = Point; module.exports = Point; - -/** - * Creates a clone of this point - * - * @return {PIXI.Point} a copy of the point - */ -Point.prototype.clone = function () -{ - return new Point(this.x, this.y); -}; - -/** - * Copies x and y from the given point - * - * @param p {PIXI.Point} - */ -Point.prototype.copy = function (p) { - this.set(p.x, p.y); -}; - -/** - * Returns true if the given point is equal to this point - * - * @param p {PIXI.Point} - * @returns {boolean} Whether the given point equal to this point - */ -Point.prototype.equals = function (p) { - return (p.x === this.x) && (p.y === this.y); -}; - -/** - * Sets the point to a new x and y position. - * If y is omitted, both x and y will be set to x. - * - * @param [x=0] {number} position of the point on the x axis - * @param [y=0] {number} position of the point on the y axis - */ -Point.prototype.set = function (x, y) -{ - this.x = x || 0; - this.y = y || ( (y !== 0) ? this.x : 0 ) ; -}; diff --git a/src/core/math/shapes/Circle.js b/src/core/math/shapes/Circle.js index 9cf2267..d814380 100644 --- a/src/core/math/shapes/Circle.js +++ b/src/core/math/shapes/Circle.js @@ -10,80 +10,82 @@ * @param y {number} The Y coordinate of the center of this circle * @param radius {number} The radius of the circle */ -function Circle(x, y, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.radius = radius || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.CIRC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.CIRC; -} - -Circle.prototype.constructor = Circle; -module.exports = Circle; - -/** - * Creates a clone of this Circle instance - * - * @return {PIXI.Circle} a copy of the Circle - */ -Circle.prototype.clone = function () -{ - return new Circle(this.x, this.y, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this circle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Circle - */ -Circle.prototype.contains = function (x, y) -{ - if (this.radius <= 0) +class Circle { + constructor(x, y, radius) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.radius = radius || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.CIRC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.CIRC; } - var dx = (this.x - x), - dy = (this.y - y), - r2 = this.radius * this.radius; + /** + * Creates a clone of this Circle instance + * + * @return {PIXI.Circle} a copy of the Circle + */ + clone() + { + return new Circle(this.x, this.y, this.radius); + } - dx *= dx; - dy *= dy; + /** + * Checks whether the x and y coordinates given are contained within this circle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Circle + */ + contains(x, y) + { + if (this.radius <= 0) + { + return false; + } - return (dx + dy <= r2); -}; + var dx = (this.x - x), + dy = (this.y - y), + r2 = this.radius * this.radius; -/** -* Returns the framing rectangle of the circle as a Rectangle object -* -* @return {PIXI.Rectangle} the framing rectangle -*/ -Circle.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); -}; + dx *= dx; + dy *= dy; + + return (dx + dy <= r2); + } + + /** + * Returns the framing rectangle of the circle as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.radius, this.y - this.radius, this.radius * 2, this.radius * 2); + } + +} + +module.exports = Circle; diff --git a/src/core/math/shapes/Ellipse.js b/src/core/math/shapes/Ellipse.js index bde51b0..6aaa9b9 100644 --- a/src/core/math/shapes/Ellipse.js +++ b/src/core/math/shapes/Ellipse.js @@ -11,86 +11,87 @@ * @param width {number} The half width of this ellipse * @param height {number} The half height of this ellipse */ -function Ellipse(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.ELIP - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.ELIP; -} - -Ellipse.prototype.constructor = Ellipse; -module.exports = Ellipse; - -/** - * Creates a clone of this Ellipse instance - * - * @return {PIXI.Ellipse} a copy of the ellipse - */ -Ellipse.prototype.clone = function () -{ - return new Ellipse(this.x, this.y, this.width, this.height); -}; - -/** - * Checks whether the x and y coordinates given are contained within this ellipse - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coords are within this ellipse - */ -Ellipse.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Ellipse { + constructor(x, y, width, height) { - return false; + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.ELIP + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.ELIP; } - //normalize the coords to an ellipse with center 0,0 - var normx = ((x - this.x) / this.width), - normy = ((y - this.y) / this.height); + /** + * Creates a clone of this Ellipse instance + * + * @return {PIXI.Ellipse} a copy of the ellipse + */ + clone() + { + return new Ellipse(this.x, this.y, this.width, this.height); + } - normx *= normx; - normy *= normy; + /** + * Checks whether the x and y coordinates given are contained within this ellipse + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coords are within this ellipse + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } - return (normx + normy <= 1); -}; + //normalize the coords to an ellipse with center 0,0 + var normx = ((x - this.x) / this.width), + normy = ((y - this.y) / this.height); -/** - * Returns the framing rectangle of the ellipse as a Rectangle object - * - * @return {PIXI.Rectangle} the framing rectangle - */ -Ellipse.prototype.getBounds = function () -{ - return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); -}; + normx *= normx; + normy *= normy; + + return (normx + normy <= 1); + } + + /** + * Returns the framing rectangle of the ellipse as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle + */ + getBounds() + { + return new Rectangle(this.x - this.width, this.y - this.height, this.width, this.height); + } +} + +module.exports = Ellipse; diff --git a/src/core/math/shapes/Polygon.js b/src/core/math/shapes/Polygon.js index df9fcb5..453fef9 100644 --- a/src/core/math/shapes/Polygon.js +++ b/src/core/math/shapes/Polygon.js @@ -10,107 +10,110 @@ * arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` and `y` are * Numbers. */ -function Polygon(points_) -{ - // prevents an argument assignment deopt - // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - var points = points_; - - //if points isn't an array, use arguments as the array - if (!Array.isArray(points)) +class Polygon { + constructor(points_) { - // prevents an argument leak deopt - // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments - points = new Array(arguments.length); + // prevents an argument assignment deopt + // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + var points = points_; - for (var a = 0; a < points.length; ++a) { - points[a] = arguments[a]; - } - } - - // if this is an array of points, convert it to a flat array of numbers - if (points[0] instanceof Point) - { - var p = []; - for (var i = 0, il = points.length; i < il; i++) + //if points isn't an array, use arguments as the array + if (!Array.isArray(points)) { - p.push(points[i].x, points[i].y); + // prevents an argument leak deopt + // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments + points = new Array(arguments.length); + + for (var a = 0; a < points.length; ++a) { + points[a] = arguments[a]; + } } - points = p; + // if this is an array of points, convert it to a flat array of numbers + if (points[0] instanceof Point) + { + var p = []; + for (var i = 0, il = points.length; i < il; i++) + { + p.push(points[i].x, points[i].y); + } + + points = p; + } + + this.closed = true; + + /** + * An array of the points of this polygon + * + * @member {number[]} + */ + this.points = points; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.POLY + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.POLY; } - this.closed = true; /** - * An array of the points of this polygon + * Creates a clone of this polygon * - * @member {number[]} + * @return {PIXI.Polygon} a copy of the polygon */ - this.points = points; + clone() + { + return new Polygon(this.points.slice()); + } + + + close() + { + var points = this.points; + + // close the poly if the value is true! + if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) + { + points.push(points[0], points[1]); + } + } /** - * The type of the object, mainly used to avoid `instanceof` checks + * Checks whether the x and y coordinates passed to this function are contained within this polygon * - * @member {number} - * @readOnly - * @default CONST.SHAPES.POLY - * @see PIXI.SHAPES + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this polygon */ - this.type = CONST.SHAPES.POLY; + contains(x, y) + { + var inside = false; + + // use some raycasting to test hits + // https://github.com/substack/point-in-polygon/blob/master/index.js + var length = this.points.length / 2; + + for (var i = 0, j = length - 1; i < length; j = i++) + { + var xi = this.points[i * 2], yi = this.points[i * 2 + 1], + xj = this.points[j * 2], yj = this.points[j * 2 + 1], + intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + + if (intersect) + { + inside = !inside; + } + } + + return inside; + } + } -Polygon.prototype.constructor = Polygon; module.exports = Polygon; - -/** - * Creates a clone of this polygon - * - * @return {PIXI.Polygon} a copy of the polygon - */ -Polygon.prototype.clone = function () -{ - return new Polygon(this.points.slice()); -}; - - -Polygon.prototype.close = function () -{ - var points = this.points; - - // close the poly if the value is true! - if (points[0] !== points[points.length-2] || points[1] !== points[points.length-1]) - { - points.push(points[0], points[1]); - } -}; - -/** - * Checks whether the x and y coordinates passed to this function are contained within this polygon - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this polygon - */ -Polygon.prototype.contains = function (x, y) -{ - var inside = false; - - // use some raycasting to test hits - // https://github.com/substack/point-in-polygon/blob/master/index.js - var length = this.points.length / 2; - - for (var i = 0, j = length - 1; i < length; j = i++) - { - var xi = this.points[i * 2], yi = this.points[i * 2 + 1], - xj = this.points[j * 2], yj = this.points[j * 2 + 1], - intersect = ((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - - if (intersect) - { - inside = !inside; - } - } - - return inside; -}; diff --git a/src/core/math/shapes/Rectangle.js b/src/core/math/shapes/Rectangle.js index 989db92..bf48bd4 100644 --- a/src/core/math/shapes/Rectangle.js +++ b/src/core/math/shapes/Rectangle.js @@ -10,164 +10,167 @@ * @param width {number} The overall width of this rectangle * @param height {number} The overall height of this rectangle */ -function Rectangle(x, y, width, height) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readOnly - * @default CONST.SHAPES.RECT - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RECT; -} - -Rectangle.prototype.constructor = Rectangle; -module.exports = Rectangle; - -/** - * A constant empty rectangle. - * - * @static - * @constant - */ -Rectangle.EMPTY = new Rectangle(0, 0, 0, 0); - - -/** - * Creates a clone of this Rectangle - * - * @return {PIXI.Rectangle} a copy of the rectangle - */ -Rectangle.prototype.clone = function () -{ - return new Rectangle(this.x, this.y, this.width, this.height); -}; - -Rectangle.prototype.copy = function (rectangle) -{ - this.x = rectangle.x; - this.y = rectangle.y; - this.width = rectangle.width; - this.height = rectangle.height; - - return this; -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rectangle - */ -Rectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class Rectangle { + constructor(x, y, width, height) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readOnly + * @default CONST.SHAPES.RECT + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RECT; + } + + /** + * A constant empty rectangle. + * + * @static + * @constant + */ + static get EMPTY() { + return new Rectangle(0, 0, 0, 0); + } + + /** + * Creates a clone of this Rectangle + * + * @return {PIXI.Rectangle} a copy of the rectangle + */ + clone() + { + return new Rectangle(this.x, this.y, this.width, this.height); + } + + copy(rectangle) + { + this.x = rectangle.x; + this.y = rectangle.y; + this.width = rectangle.width; + this.height = rectangle.height; + + return this; + } + + /** + * Checks whether the x and y coordinates given are contained within this Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x < this.x + this.width) + { + if (y >= this.y && y < this.y + this.height) + { + return true; + } + } + return false; } - if (x >= this.x && x < this.x + this.width) + pad(paddingX, paddingY) { - if (y >= this.y && y < this.y + this.height) + paddingX = paddingX || 0; + paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); + + this.x -= paddingX; + this.y -= paddingY; + + this.width += paddingX * 2; + this.height += paddingY * 2; + } + + fit(rectangle) + { + if (this.x < rectangle.x) { - return true; + this.width += this.x; + if(this.width < 0) { + this.width = 0; + } + + this.x = rectangle.x; + } + + if (this.y < rectangle.y) + { + this.height += this.y; + if(this.height < 0) { + this.height = 0; + } + this.y = rectangle.y; + } + + if ( this.x + this.width > rectangle.x + rectangle.width ) + { + this.width = rectangle.width - this.x; + if(this.width < 0) { + this.width = 0; + } + } + + if ( this.y + this.height > rectangle.y + rectangle.height ) + { + this.height = rectangle.height - this.y; + if(this.height < 0) { + this.height = 0; + } } } - return false; -}; - -Rectangle.prototype.pad = function (paddingX, paddingY) -{ - paddingX = paddingX || 0; - paddingY = paddingY || ( (paddingY !== 0) ? paddingX : 0 ); - - this.x -= paddingX; - this.y -= paddingY; - - this.width += paddingX * 2; - this.height += paddingY * 2; -}; - -Rectangle.prototype.fit = function (rectangle) -{ - if (this.x < rectangle.x) + enlarge(rect) { - this.width += this.x; - if(this.width < 0) { - this.width = 0; + + if (rect === Rectangle.EMPTY) + { + return; } - this.x = rectangle.x; + var x1 = Math.min(this.x, rect.x); + var x2 = Math.max(this.x + this.width, rect.x + rect.width); + var y1 = Math.min(this.y, rect.y); + var y2 = Math.max(this.y + this.height, rect.y + rect.height); + this.x = x1; + this.width = x2 - x1; + this.y = y1; + this.height = y2 - y1; } - if (this.y < rectangle.y) - { - this.height += this.y; - if(this.height < 0) { - this.height = 0; - } - this.y = rectangle.y; - } +} - if ( this.x + this.width > rectangle.x + rectangle.width ) - { - this.width = rectangle.width - this.x; - if(this.width < 0) { - this.width = 0; - } - } - - if ( this.y + this.height > rectangle.y + rectangle.height ) - { - this.height = rectangle.height - this.y; - if(this.height < 0) { - this.height = 0; - } - } -}; - -Rectangle.prototype.enlarge = function (rect) -{ - - if (rect === Rectangle.EMPTY) - { - return; - } - - var x1 = Math.min(this.x, rect.x); - var x2 = Math.max(this.x + this.width, rect.x + rect.width); - var y1 = Math.min(this.y, rect.y); - var y2 = Math.max(this.y + this.height, rect.y + rect.height); - this.x = x1; - this.width = x2 - x1; - this.y = y1; - this.height = y2 - y1; -}; +module.exports = Rectangle; diff --git a/src/core/math/shapes/RoundedRectangle.js b/src/core/math/shapes/RoundedRectangle.js index 0d76457..574c9be 100644 --- a/src/core/math/shapes/RoundedRectangle.js +++ b/src/core/math/shapes/RoundedRectangle.js @@ -11,83 +11,85 @@ * @param height {number} The overall height of this rounded rectangle * @param radius {number} Controls the radius of the rounded corners */ -function RoundedRectangle(x, y, width, height, radius) -{ - /** - * @member {number} - * @default 0 - */ - this.x = x || 0; - - /** - * @member {number} - * @default 0 - */ - this.y = y || 0; - - /** - * @member {number} - * @default 0 - */ - this.width = width || 0; - - /** - * @member {number} - * @default 0 - */ - this.height = height || 0; - - /** - * @member {number} - * @default 20 - */ - this.radius = radius || 20; - - /** - * The type of the object, mainly used to avoid `instanceof` checks - * - * @member {number} - * @readonly - * @default CONST.SHAPES.RREC - * @see PIXI.SHAPES - */ - this.type = CONST.SHAPES.RREC; -} - -RoundedRectangle.prototype.constructor = RoundedRectangle; -module.exports = RoundedRectangle; - -/** - * Creates a clone of this Rounded Rectangle - * - * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle - */ -RoundedRectangle.prototype.clone = function () -{ - return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); -}; - -/** - * Checks whether the x and y coordinates given are contained within this Rounded Rectangle - * - * @param x {number} The X coordinate of the point to test - * @param y {number} The Y coordinate of the point to test - * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle - */ -RoundedRectangle.prototype.contains = function (x, y) -{ - if (this.width <= 0 || this.height <= 0) +class RoundedRectangle { + constructor(x, y, width, height, radius) { + /** + * @member {number} + * @default 0 + */ + this.x = x || 0; + + /** + * @member {number} + * @default 0 + */ + this.y = y || 0; + + /** + * @member {number} + * @default 0 + */ + this.width = width || 0; + + /** + * @member {number} + * @default 0 + */ + this.height = height || 0; + + /** + * @member {number} + * @default 20 + */ + this.radius = radius || 20; + + /** + * The type of the object, mainly used to avoid `instanceof` checks + * + * @member {number} + * @readonly + * @default CONST.SHAPES.RREC + * @see PIXI.SHAPES + */ + this.type = CONST.SHAPES.RREC; + } + + /** + * Creates a clone of this Rounded Rectangle + * + * @return {PIXI.RoundedRectangle} a copy of the rounded rectangle + */ + clone() + { + return new RoundedRectangle(this.x, this.y, this.width, this.height, this.radius); + } + + /** + * Checks whether the x and y coordinates given are contained within this Rounded Rectangle + * + * @param x {number} The X coordinate of the point to test + * @param y {number} The Y coordinate of the point to test + * @return {boolean} Whether the x/y coordinates are within this Rounded Rectangle + */ + contains(x, y) + { + if (this.width <= 0 || this.height <= 0) + { + return false; + } + + if (x >= this.x && x <= this.x + this.width) + { + if (y >= this.y && y <= this.y + this.height) + { + return true; + } + } + return false; } + +} - if (x >= this.x && x <= this.x + this.width) - { - if (y >= this.y && y <= this.y + this.height) - { - return true; - } - } - - return false; -}; +module.exports = RoundedRectangle; diff --git a/src/core/renderers/SystemRenderer.js b/src/core/renderers/SystemRenderer.js index c50e26a..a5c9bd1 100644 --- a/src/core/renderers/SystemRenderer.js +++ b/src/core/renderers/SystemRenderer.js @@ -25,161 +25,244 @@ * @param [options.backgroundColor=0x000000] {number} The background color of the rendered area (shown if not transparent). * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function SystemRenderer(system, width, height, options) -{ - EventEmitter.call(this); - - utils.sayHello(system); - - // prepare options - if (options) +class SystemRenderer extends EventEmitter { + constructor(system, width, height, options) { - for (var i in CONST.DEFAULT_RENDER_OPTIONS) + super(); + + utils.sayHello(system); + + // prepare options + if (options) { - if (typeof options[i] === 'undefined') + for (var i in CONST.DEFAULT_RENDER_OPTIONS) { - options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + if (typeof options[i] === 'undefined') + { + options[i] = CONST.DEFAULT_RENDER_OPTIONS[i]; + } } } - } - else - { - options = CONST.DEFAULT_RENDER_OPTIONS; + else + { + options = CONST.DEFAULT_RENDER_OPTIONS; + } + + /** + * The type of the renderer. + * + * @member {number} + * @default PIXI.RENDERER_TYPE.UNKNOWN + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.UNKNOWN; + + /** + * The width of the canvas view + * + * @member {number} + * @default 800 + */ + this.width = width || 800; + + /** + * The height of the canvas view + * + * @member {number} + * @default 600 + */ + this.height = height || 600; + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.view = options.view || document.createElement('canvas'); + + /** + * The resolution / device pixel ratio of the renderer + * + * @member {number} + * @default 1 + */ + this.resolution = options.resolution; + + /** + * Whether the render view is transparent + * + * @member {boolean} + */ + this.transparent = options.transparent; + + /** + * Whether the render view should be resized automatically + * + * @member {boolean} + */ + this.autoResize = options.autoResize || false; + + /** + * Tracks the blend modes useful for this renderer. + * + * @member {object} + */ + this.blendModes = null; + + /** + * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. + * + * @member {boolean} + */ + this.preserveDrawingBuffer = options.preserveDrawingBuffer; + + /** + * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. + * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. + * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. + * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. + * + * @member {boolean} + * @default + */ + this.clearBeforeRender = options.clearBeforeRender; + + /** + * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. + * Handy for crisp pixel art and speed on legacy devices. + * + * @member {boolean} + */ + this.roundPixels = options.roundPixels; + + /** + * The background color as a number. + * + * @member {number} + * @private + */ + this._backgroundColor = 0x000000; + + /** + * The background color as an [R, G, B] array. + * + * @member {number[]} + * @private + */ + this._backgroundColorRgba = [0, 0, 0, 0]; + + /** + * The background color as a string. + * + * @member {string} + * @private + */ + this._backgroundColorString = '#000000'; + + this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter + + /** + * This temporary display object used as the parent of the currently being rendered item + * + * @member {PIXI.DisplayObject} + * @private + */ + this._tempDisplayObjectParent = new Container(); + + /** + * The last root object that the renderer tried to render. + * + * @member {PIXI.DisplayObject} + * @private + */ + this._lastObjectRendered = this._tempDisplayObjectParent; } /** - * The type of the renderer. + * Resizes the canvas view to the specified width and height * - * @member {number} - * @default PIXI.RENDERER_TYPE.UNKNOWN - * @see PIXI.RENDERER_TYPE + * @param width {number} the new width of the canvas view + * @param height {number} the new height of the canvas view */ - this.type = CONST.RENDERER_TYPE.UNKNOWN; + resize(width, height) { + this.width = width * this.resolution; + this.height = height * this.resolution; + + this.view.width = this.width; + this.view.height = this.height; + + if (this.autoResize) + { + this.view.style.width = this.width / this.resolution + 'px'; + this.view.style.height = this.height / this.resolution + 'px'; + } + } /** - * The width of the canvas view + * Useful function that returns a texture of the display object that can then be used to create sprites + * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. * - * @member {number} - * @default 800 + * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from + * @param scaleMode {number} Should be one of the scaleMode consts + * @param resolution {number} The resolution / device pixel ratio of the texture being generated + * @return {PIXI.Texture} a texture of the graphics object */ - this.width = width || 800; + generateTexture(displayObject, scaleMode, resolution) { + + var bounds = displayObject.getLocalBounds(); + + var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); + + tempMatrix.tx = -bounds.x; + tempMatrix.ty = -bounds.y; + + this.render(displayObject, renderTexture, false, tempMatrix, true); + + return renderTexture; + } /** - * The height of the canvas view + * Removes everything from the renderer and optionally removes the Canvas DOM element. * - * @member {number} - * @default 600 + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. */ - this.height = height || 600; + destroy(removeView) { + if (removeView && this.view.parentNode) + { + this.view.parentNode.removeChild(this.view); + } - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.view = options.view || document.createElement('canvas'); + this.type = CONST.RENDERER_TYPE.UNKNOWN; - /** - * The resolution / device pixel ratio of the renderer - * - * @member {number} - * @default 1 - */ - this.resolution = options.resolution; + this.width = 0; + this.height = 0; - /** - * Whether the render view is transparent - * - * @member {boolean} - */ - this.transparent = options.transparent; + this.view = null; - /** - * Whether the render view should be resized automatically - * - * @member {boolean} - */ - this.autoResize = options.autoResize || false; + this.resolution = 0; - /** - * Tracks the blend modes useful for this renderer. - * - * @member {object} - */ - this.blendModes = null; + this.transparent = false; - /** - * The value of the preserveDrawingBuffer flag affects whether or not the contents of the stencil buffer is retained after rendering. - * - * @member {boolean} - */ - this.preserveDrawingBuffer = options.preserveDrawingBuffer; + this.autoResize = false; - /** - * This sets if the CanvasRenderer will clear the canvas or not before the new render pass. - * If the scene is NOT transparent Pixi will use a canvas sized fillRect operation every frame to set the canvas background color. - * If the scene is transparent Pixi will use clearRect to clear the canvas every frame. - * Disable this by setting this to false. For example if your game has a canvas filling background image you often don't need this set. - * - * @member {boolean} - * @default - */ - this.clearBeforeRender = options.clearBeforeRender; + this.blendModes = null; - /** - * If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. - * Handy for crisp pixel art and speed on legacy devices. - * - * @member {boolean} - */ - this.roundPixels = options.roundPixels; + this.preserveDrawingBuffer = false; + this.clearBeforeRender = false; - /** - * The background color as a number. - * - * @member {number} - * @private - */ - this._backgroundColor = 0x000000; + this.roundPixels = false; - /** - * The background color as an [R, G, B] array. - * - * @member {number[]} - * @private - */ - this._backgroundColorRgba = [0, 0, 0, 0]; + this._backgroundColor = 0; + this._backgroundColorRgba = null; + this._backgroundColorString = null; - /** - * The background color as a string. - * - * @member {string} - * @private - */ - this._backgroundColorString = '#000000'; + this.backgroundColor = 0; + this._tempDisplayObjectParent = null; + this._lastObjectRendered = null; + } - this.backgroundColor = options.backgroundColor || this._backgroundColor; // run bg color setter - - /** - * This temporary display object used as the parent of the currently being rendered item - * - * @member {PIXI.DisplayObject} - * @private - */ - this._tempDisplayObjectParent = new Container(); - - /** - * The last root object that the renderer tried to render. - * - * @member {PIXI.DisplayObject} - * @private - */ - this._lastObjectRendered = this._tempDisplayObjectParent; } -// constructor -SystemRenderer.prototype = Object.create(EventEmitter.prototype); -SystemRenderer.prototype.constructor = SystemRenderer; module.exports = SystemRenderer; Object.defineProperties(SystemRenderer.prototype, { @@ -203,86 +286,3 @@ } } }); - -/** - * Resizes the canvas view to the specified width and height - * - * @param width {number} the new width of the canvas view - * @param height {number} the new height of the canvas view - */ -SystemRenderer.prototype.resize = function (width, height) { - this.width = width * this.resolution; - this.height = height * this.resolution; - - this.view.width = this.width; - this.view.height = this.height; - - if (this.autoResize) - { - this.view.style.width = this.width / this.resolution + 'px'; - this.view.style.height = this.height / this.resolution + 'px'; - } -}; - -/** - * Useful function that returns a texture of the display object that can then be used to create sprites - * This can be quite useful if your displayObject is complicated and needs to be reused multiple times. - * - * @param displayObject {PIXI.DisplayObject} The displayObject the object will be generated from - * @param scaleMode {number} Should be one of the scaleMode consts - * @param resolution {number} The resolution / device pixel ratio of the texture being generated - * @return {PIXI.Texture} a texture of the graphics object - */ -SystemRenderer.prototype.generateTexture = function (displayObject, scaleMode, resolution) { - - var bounds = displayObject.getLocalBounds(); - - var renderTexture = RenderTexture.create(bounds.width | 0, bounds.height | 0, scaleMode, resolution); - - tempMatrix.tx = -bounds.x; - tempMatrix.ty = -bounds.y; - - this.render(displayObject, renderTexture, false, tempMatrix, true); - - return renderTexture; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -SystemRenderer.prototype.destroy = function (removeView) { - if (removeView && this.view.parentNode) - { - this.view.parentNode.removeChild(this.view); - } - - this.type = CONST.RENDERER_TYPE.UNKNOWN; - - this.width = 0; - this.height = 0; - - this.view = null; - - this.resolution = 0; - - this.transparent = false; - - this.autoResize = false; - - this.blendModes = null; - - this.preserveDrawingBuffer = false; - this.clearBeforeRender = false; - - this.roundPixels = false; - - this._backgroundColor = 0; - this._backgroundColorRgba = null; - this._backgroundColorString = null; - - this.backgroundColor = 0; - this._tempDisplayObjectParent = null; - this._lastObjectRendered = null; -}; diff --git a/src/core/renderers/canvas/CanvasRenderer.js b/src/core/renderers/canvas/CanvasRenderer.js index de371c7..3038d4d 100644 --- a/src/core/renderers/canvas/CanvasRenderer.js +++ b/src/core/renderers/canvas/CanvasRenderer.js @@ -24,240 +24,239 @@ * not before the new render pass. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function CanvasRenderer(width, height, options) -{ - options = options || {}; +class CanvasRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'Canvas', width, height, options); + super('Canvas', width, height, options); - this.type = CONST.RENDERER_TYPE.CANVAS; + this.type = CONST.RENDERER_TYPE.CANVAS; + + /** + * The canvas 2d context that everything is drawn with. + * + * @member {CanvasRenderingContext2D} + */ + this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); + this.rootResolution = this.resolution; + + /** + * Boolean flag controlling canvas refresh. + * + * @member {boolean} + */ + this.refresh = true; + + /** + * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. + * + * @member {PIXI.CanvasMaskManager} + */ + this.maskManager = new CanvasMaskManager(this); + + /** + * The canvas property used to set the canvas smoothing property. + * + * @member {string} + */ + this.smoothProperty = 'imageSmoothingEnabled'; + + if (!this.rootContext.imageSmoothingEnabled) + { + if (this.rootContext.webkitImageSmoothingEnabled) + { + this.smoothProperty = 'webkitImageSmoothingEnabled'; + } + else if (this.rootContext.mozImageSmoothingEnabled) + { + this.smoothProperty = 'mozImageSmoothingEnabled'; + } + else if (this.rootContext.oImageSmoothingEnabled) + { + this.smoothProperty = 'oImageSmoothingEnabled'; + } + else if (this.rootContext.msImageSmoothingEnabled) + { + this.smoothProperty = 'msImageSmoothingEnabled'; + } + } + + this.initPlugins(); + + this.blendModes = mapCanvasBlendModesToPixi(); + this._activeBlendMode = null; + + this.context = null; + this.renderingToScreen = false; + + this.resize(width, height); + } /** - * The canvas 2d context that everything is drawn with. + * Renders the object to this canvas view * - * @member {CanvasRenderingContext2D} + * @param displayObject {PIXI.DisplayObject} The object to be rendered + * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. + * @param [clear=false] {boolean} Whether to clear the canvas before drawing + * @param [transform] {PIXI.Transform} A transformation to be applied + * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform */ - this.rootContext = this.view.getContext('2d', { alpha: this.transparent }); - this.rootResolution = this.resolution; - - /** - * Boolean flag controlling canvas refresh. - * - * @member {boolean} - */ - this.refresh = true; - - /** - * Instance of a CanvasMaskManager, handles masking when using the canvas renderer. - * - * @member {PIXI.CanvasMaskManager} - */ - this.maskManager = new CanvasMaskManager(this); - - /** - * The canvas property used to set the canvas smoothing property. - * - * @member {string} - */ - this.smoothProperty = 'imageSmoothingEnabled'; - - if (!this.rootContext.imageSmoothingEnabled) - { - if (this.rootContext.webkitImageSmoothingEnabled) - { - this.smoothProperty = 'webkitImageSmoothingEnabled'; - } - else if (this.rootContext.mozImageSmoothingEnabled) - { - this.smoothProperty = 'mozImageSmoothingEnabled'; - } - else if (this.rootContext.oImageSmoothingEnabled) - { - this.smoothProperty = 'oImageSmoothingEnabled'; - } - else if (this.rootContext.msImageSmoothingEnabled) - { - this.smoothProperty = 'msImageSmoothingEnabled'; - } - } - - this.initPlugins(); - - this.blendModes = mapCanvasBlendModesToPixi(); - this._activeBlendMode = null; - - this.context = null; - this.renderingToScreen = false; - - this.resize(width, height); -} - -// constructor -CanvasRenderer.prototype = Object.create(SystemRenderer.prototype); -CanvasRenderer.prototype.constructor = CanvasRenderer; -module.exports = CanvasRenderer; -utils.pluginTarget.mixin(CanvasRenderer); - - -/** - * Renders the object to this canvas view - * - * @param displayObject {PIXI.DisplayObject} The object to be rendered - * @param [renderTexture] {PIXI.RenderTexture} A render texture to be rendered to. If unset, it will render to the root context. - * @param [clear=false] {boolean} Whether to clear the canvas before drawing - * @param [transform] {PIXI.Transform} A transformation to be applied - * @param [skipUpdateTransform=false] {boolean} Whether to skip the update transform - */ -CanvasRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - if (!this.view){ - return; - } - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - if(renderTexture) - { - renderTexture = renderTexture.baseTexture || renderTexture; - - if(!renderTexture._canvasRenderTarget) - { - - renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); - renderTexture.source = renderTexture._canvasRenderTarget.canvas; - renderTexture.valid = true; - } - - this.context = renderTexture._canvasRenderTarget.context; - this.resolution = renderTexture._canvasRenderTarget.resolution; - } - else + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) { - this.context = this.rootContext; - this.resolution = this.rootResolution; - } + if (!this.view){ + return; + } - var context = this.context; + // can be handy to know! + this.renderingToScreen = !renderTexture; - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } + this.emit('prerender'); - - - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - var tempWt = this._tempDisplayObjectParent.transform.worldTransform; - - if(transform) + if(renderTexture) { - transform.copy(tempWt); + renderTexture = renderTexture.baseTexture || renderTexture; + + if(!renderTexture._canvasRenderTarget) + { + + renderTexture._canvasRenderTarget = new CanvasRenderTarget(renderTexture.width, renderTexture.height, renderTexture.resolution); + renderTexture.source = renderTexture._canvasRenderTarget.canvas; + renderTexture.valid = true; + } + + this.context = renderTexture._canvasRenderTarget.context; + this.resolution = renderTexture._canvasRenderTarget.resolution; } else { - tempWt.identity(); + + this.context = this.rootContext; + this.resolution = this.rootResolution; } - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } + var context = this.context; + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } - context.setTransform(1, 0, 0, 1, 0, 0); - context.globalAlpha = 1; - context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; - if (navigator.isCocoonJS && this.view.screencanvas) - { - context.fillStyle = 'black'; - context.clear(); - } - if(clear !== undefined ? clear : this.clearBeforeRender) - { - if (this.renderingToScreen) { - if (this.transparent) { - context.clearRect(0, 0, this.width, this.height); + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + var tempWt = this._tempDisplayObjectParent.transform.worldTransform; + + if(transform) + { + transform.copy(tempWt); } - else { - context.fillStyle = this._backgroundColorString; - context.fillRect(0, 0, this.width, this.height); + else + { + tempWt.identity(); } - } //else { - //TODO: implement background for CanvasRenderTarget or RenderTexture? - //} + + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + + context.setTransform(1, 0, 0, 1, 0, 0); + context.globalAlpha = 1; + context.globalCompositeOperation = this.blendModes[CONST.BLEND_MODES.NORMAL]; + + if (navigator.isCocoonJS && this.view.screencanvas) + { + context.fillStyle = 'black'; + context.clear(); + } + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + if (this.renderingToScreen) { + if (this.transparent) { + context.clearRect(0, 0, this.width, this.height); + } + else { + context.fillStyle = this._backgroundColorString; + context.fillRect(0, 0, this.width, this.height); + } + } //else { + //TODO: implement background for CanvasRenderTarget or RenderTexture? + //} + } + + // TODO RENDER TARGET STUFF HERE.. + var tempContext = this.context; + + this.context = context; + displayObject.renderCanvas(this); + this.context = tempContext; + + this.emit('postrender'); } - // TODO RENDER TARGET STUFF HERE.. - var tempContext = this.context; - this.context = context; - displayObject.renderCanvas(this); - this.context = tempContext; - - this.emit('postrender'); -}; - - -CanvasRenderer.prototype.setBlendMode = function (blendMode) -{ - if(this._activeBlendMode === blendMode) { - return; - } - - this.context.globalCompositeOperation = this.blendModes[blendMode]; -}; - -/** - * Removes everything from the renderer and optionally removes the Canvas DOM element. - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. - */ -CanvasRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // call the base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.context = null; - - this.refresh = true; - - this.maskManager.destroy(); - this.maskManager = null; - - this.smoothProperty = null; -}; - -/** - * Resizes the canvas view to the specified width and height. - * - * @extends PIXI.SystemRenderer#resize - * - * @param width {number} The new width of the canvas view - * @param height {number} The new height of the canvas view - */ -CanvasRenderer.prototype.resize = function (width, height) -{ - SystemRenderer.prototype.resize.call(this, width, height); - - //reset the scale mode.. oddly this seems to be reset when the canvas is resized. - //surely a browser bug?? Let pixi fix that for you.. - if(this.smoothProperty) + setBlendMode(blendMode) { - this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + if(this._activeBlendMode === blendMode) { + return; + } + + this.context.globalCompositeOperation = this.blendModes[blendMode]; } -}; + /** + * Removes everything from the renderer and optionally removes the Canvas DOM element. + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. + */ + destroy(removeView) + { + this.destroyPlugins(); + + // call the base destroy + super.destroy(removeView); + + this.context = null; + + this.refresh = true; + + this.maskManager.destroy(); + this.maskManager = null; + + this.smoothProperty = null; + } + + /** + * Resizes the canvas view to the specified width and height. + * + * @extends PIXI.SystemRenderer#resize + * + * @param width {number} The new width of the canvas view + * @param height {number} The new height of the canvas view + */ + resize(width, height) + { + super.resize(width, height); + + //reset the scale mode.. oddly this seems to be reset when the canvas is resized. + //surely a browser bug?? Let pixi fix that for you.. + if(this.smoothProperty) + { + this.rootContext[this.smoothProperty] = (CONST.SCALE_MODES.DEFAULT === CONST.SCALE_MODES.LINEAR); + } + + } + +} + +module.exports = CanvasRenderer; +utils.pluginTarget.mixin(CanvasRenderer); diff --git a/src/core/renderers/canvas/utils/CanvasMaskManager.js b/src/core/renderers/canvas/utils/CanvasMaskManager.js index 27ab912..9551d5d 100644 --- a/src/core/renderers/canvas/utils/CanvasMaskManager.js +++ b/src/core/renderers/canvas/utils/CanvasMaskManager.js @@ -5,156 +5,158 @@ * @class * @memberof PIXI */ -function CanvasMaskManager(renderer) -{ - this.renderer = renderer; -} - -CanvasMaskManager.prototype.constructor = CanvasMaskManager; -module.exports = CanvasMaskManager; - -/** - * This method adds it to the current stack of masks. - * - * @param maskData {object} the maskData that will be pushed - */ -CanvasMaskManager.prototype.pushMask = function (maskData) -{ - var renderer = this.renderer; - - renderer.context.save(); - - var cacheAlpha = maskData.alpha; - var transform = maskData.transform.worldTransform; - var resolution = renderer.resolution; - - renderer.context.setTransform( - transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution - ); - - //TODO suport sprite alpha masks?? - //lots of effort required. If demand is great enough.. - if(!maskData._texture) +class CanvasMaskManager { + constructor(renderer) { - this.renderGraphicsShape(maskData); - renderer.context.clip(); + this.renderer = renderer; } - maskData.worldAlpha = cacheAlpha; -}; - -CanvasMaskManager.prototype.renderGraphicsShape = function (graphics) -{ - var context = this.renderer.context; - var len = graphics.graphicsData.length; - - if (len === 0) + /** + * This method adds it to the current stack of masks. + * + * @param maskData {object} the maskData that will be pushed + */ + pushMask(maskData) { - return; - } + var renderer = this.renderer; - context.beginPath(); + renderer.context.save(); - for (var i = 0; i < len; i++) - { - var data = graphics.graphicsData[i]; - var shape = data.shape; + var cacheAlpha = maskData.alpha; + var transform = maskData.transform.worldTransform; + var resolution = renderer.resolution; - if (data.type === CONST.SHAPES.POLY) + renderer.context.setTransform( + transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution + ); + + //TODO suport sprite alpha masks?? + //lots of effort required. If demand is great enough.. + if(!maskData._texture) { + this.renderGraphicsShape(maskData); + renderer.context.clip(); + } - var points = shape.points; + maskData.worldAlpha = cacheAlpha; + } - context.moveTo(points[0], points[1]); + renderGraphicsShape(graphics) + { + var context = this.renderer.context; + var len = graphics.graphicsData.length; - for (var j=1; j < points.length/2; j++) + if (len === 0) + { + return; + } + + context.beginPath(); + + for (var i = 0; i < len; i++) + { + var data = graphics.graphicsData[i]; + var shape = data.shape; + + if (data.type === CONST.SHAPES.POLY) { - context.lineTo(points[j * 2], points[j * 2 + 1]); + + var points = shape.points; + + context.moveTo(points[0], points[1]); + + for (var j=1; j < points.length/2; j++) + { + context.lineTo(points[j * 2], points[j * 2 + 1]); + } + + // if the first and last point are the same close the path - much neater :) + if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + { + context.closePath(); + } + } - - // if the first and last point are the same close the path - much neater :) - if (points[0] === points[points.length-2] && points[1] === points[points.length-1]) + else if (data.type === CONST.SHAPES.RECT) { + context.rect(shape.x, shape.y, shape.width, shape.height); context.closePath(); } + else if (data.type === CONST.SHAPES.CIRC) + { + // TODO - need to be Undefined! + context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); + context.closePath(); + } + else if (data.type === CONST.SHAPES.ELIP) + { - } - else if (data.type === CONST.SHAPES.RECT) - { - context.rect(shape.x, shape.y, shape.width, shape.height); - context.closePath(); - } - else if (data.type === CONST.SHAPES.CIRC) - { - // TODO - need to be Undefined! - context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI); - context.closePath(); - } - else if (data.type === CONST.SHAPES.ELIP) - { + // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas - // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas + var w = shape.width * 2; + var h = shape.height * 2; - var w = shape.width * 2; - var h = shape.height * 2; + var x = shape.x - w/2; + var y = shape.y - h/2; - var x = shape.x - w/2; - var y = shape.y - h/2; + var kappa = 0.5522848, + ox = (w / 2) * kappa, // control point offset horizontal + oy = (h / 2) * kappa, // control point offset vertical + xe = x + w, // x-end + ye = y + h, // y-end + xm = x + w / 2, // x-middle + ym = y + h / 2; // y-middle - var kappa = 0.5522848, - ox = (w / 2) * kappa, // control point offset horizontal - oy = (h / 2) * kappa, // control point offset vertical - xe = x + w, // x-end - ye = y + h, // y-end - xm = x + w / 2, // x-middle - ym = y + h / 2; // y-middle + context.moveTo(x, ym); + context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); + context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); + context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); + context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); + context.closePath(); + } + else if (data.type === CONST.SHAPES.RREC) + { - context.moveTo(x, ym); - context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); - context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); - context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); - context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); - context.closePath(); - } - else if (data.type === CONST.SHAPES.RREC) - { + var rx = shape.x; + var ry = shape.y; + var width = shape.width; + var height = shape.height; + var radius = shape.radius; - var rx = shape.x; - var ry = shape.y; - var width = shape.width; - var height = shape.height; - var radius = shape.radius; + var maxRadius = Math.min(width, height) / 2 | 0; + radius = radius > maxRadius ? maxRadius : radius; - var maxRadius = Math.min(width, height) / 2 | 0; - radius = radius > maxRadius ? maxRadius : radius; - - context.moveTo(rx, ry + radius); - context.lineTo(rx, ry + height - radius); - context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); - context.lineTo(rx + width - radius, ry + height); - context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); - context.lineTo(rx + width, ry + radius); - context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); - context.lineTo(rx + radius, ry); - context.quadraticCurveTo(rx, ry, rx, ry + radius); - context.closePath(); + context.moveTo(rx, ry + radius); + context.lineTo(rx, ry + height - radius); + context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height); + context.lineTo(rx + width - radius, ry + height); + context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius); + context.lineTo(rx + width, ry + radius); + context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry); + context.lineTo(rx + radius, ry); + context.quadraticCurveTo(rx, ry, rx, ry + radius); + context.closePath(); + } } } -}; -/** - * Restores the current drawing context to the state it was before the mask was applied. - * - * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. - */ -CanvasMaskManager.prototype.popMask = function (renderer) -{ - renderer.context.restore(); -}; + /** + * Restores the current drawing context to the state it was before the mask was applied. + * + * @param renderer {PIXI.WebGLRenderer|PIXI.CanvasRenderer} The renderer context to use. + */ + popMask(renderer) + { + renderer.context.restore(); + } -CanvasMaskManager.prototype.destroy = function () {}; + destroy() {} + +} + +module.exports = CanvasMaskManager; diff --git a/src/core/renderers/canvas/utils/CanvasRenderTarget.js b/src/core/renderers/canvas/utils/CanvasRenderTarget.js index 958106a..46f0ab6 100644 --- a/src/core/renderers/canvas/utils/CanvasRenderTarget.js +++ b/src/core/renderers/canvas/utils/CanvasRenderTarget.js @@ -9,28 +9,64 @@ * @param height {number} the height for the newly created canvas * @param [resolution=1] The resolution / device pixel ratio of the canvas */ -function CanvasRenderTarget(width, height, resolution) -{ - /** - * The Canvas object that belongs to this CanvasRenderTarget. - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class CanvasRenderTarget { + constructor(width, height, resolution) + { + /** + * The Canvas object that belongs to this CanvasRenderTarget. + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * + * @member {CanvasRenderingContext2D} + */ + this.context = this.canvas.getContext('2d'); + + this.resolution = resolution || CONST.RESOLUTION; + + this.resize(width, height); + } /** - * A CanvasRenderingContext2D object representing a two-dimensional rendering context. + * Clears the canvas that was created by the CanvasRenderTarget class. * - * @member {CanvasRenderingContext2D} + * @private */ - this.context = this.canvas.getContext('2d'); + clear() + { + this.context.setTransform(1, 0, 0, 1, 0, 0); + this.context.clearRect(0,0, this.canvas.width, this.canvas.height); + } - this.resolution = resolution || CONST.RESOLUTION; + /** + * Resizes the canvas to the specified width and height. + * + * @param width {number} the new width of the canvas + * @param height {number} the new height of the canvas + */ + resize(width, height) + { - this.resize(width, height); + this.canvas.width = width * this.resolution; + this.canvas.height = height * this.resolution; + } + + /** + * Destroys this canvas. + * + */ + destroy() + { + this.context = null; + this.canvas = null; + } + } -CanvasRenderTarget.prototype.constructor = CanvasRenderTarget; module.exports = CanvasRenderTarget; Object.defineProperties(CanvasRenderTarget.prototype, { @@ -67,37 +103,3 @@ } } }); - -/** - * Clears the canvas that was created by the CanvasRenderTarget class. - * - * @private - */ -CanvasRenderTarget.prototype.clear = function () -{ - this.context.setTransform(1, 0, 0, 1, 0, 0); - this.context.clearRect(0,0, this.canvas.width, this.canvas.height); -}; - -/** - * Resizes the canvas to the specified width and height. - * - * @param width {number} the new width of the canvas - * @param height {number} the new height of the canvas - */ -CanvasRenderTarget.prototype.resize = function (width, height) -{ - - this.canvas.width = width * this.resolution; - this.canvas.height = height * this.resolution; -}; - -/** - * Destroys this canvas. - * - */ -CanvasRenderTarget.prototype.destroy = function () -{ - this.context = null; - this.canvas = null; -}; diff --git a/src/core/renderers/webgl/TextureGarbageCollector.js b/src/core/renderers/webgl/TextureGarbageCollector.js index 361b8e5..6cc37c8 100644 --- a/src/core/renderers/webgl/TextureGarbageCollector.js +++ b/src/core/renderers/webgl/TextureGarbageCollector.js @@ -8,102 +8,104 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function TextureGarbageCollector(renderer) -{ - this.renderer = renderer; - - this.count = 0; - this.checkCount = 0; - this.maxIdle = 60 * 60; - this.checkCountMax = 60 * 10; - - this.mode = CONST.GC_MODES.DEFAULT; -} - -TextureGarbageCollector.prototype.constructor = TextureGarbageCollector; -module.exports = TextureGarbageCollector; - -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.update = function() -{ - this.count++; - - if(this.mode === CONST.GC_MODES.MANUAL) +class TextureGarbageCollector { + constructor(renderer) { - return; - } + this.renderer = renderer; - this.checkCount++; - - - if(this.checkCount > this.checkCountMax) - { + this.count = 0; this.checkCount = 0; + this.maxIdle = 60 * 60; + this.checkCountMax = 60 * 10; - this.run(); + this.mode = CONST.GC_MODES.DEFAULT; } -}; -/** - * Checks to see when the last time a texture was used - * if the texture has not been used for a specified amount of time it will be removed from the GPU - */ -TextureGarbageCollector.prototype.run = function() -{ - var tm = this.renderer.textureManager; - var managedTextures = tm._managedTextures; - var wasRemoved = false; - var i,j; - - for (i = 0; i < managedTextures.length; i++) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + update() { - var texture = managedTextures[i]; + this.count++; - // only supports non generated textures at the moment! - if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) + if(this.mode === CONST.GC_MODES.MANUAL) { - tm.destroyTexture(texture, true); - managedTextures[i] = null; - wasRemoved = true; + return; + } + + this.checkCount++; + + + if(this.checkCount > this.checkCountMax) + { + this.checkCount = 0; + + this.run(); } } - if (wasRemoved) + /** + * Checks to see when the last time a texture was used + * if the texture has not been used for a specified amount of time it will be removed from the GPU + */ + run() { - j = 0; + var tm = this.renderer.textureManager; + var managedTextures = tm._managedTextures; + var wasRemoved = false; + var i,j; for (i = 0; i < managedTextures.length; i++) { - if (managedTextures[i] !== null) + var texture = managedTextures[i]; + + // only supports non generated textures at the moment! + if (!texture._glRenderTargets && this.count - texture.touched > this.maxIdle) { - managedTextures[j++] = managedTextures[i]; + tm.destroyTexture(texture, true); + managedTextures[i] = null; + wasRemoved = true; } } - managedTextures.length = j; + if (wasRemoved) + { + j = 0; + + for (i = 0; i < managedTextures.length; i++) + { + if (managedTextures[i] !== null) + { + managedTextures[j++] = managedTextures[i]; + } + } + + managedTextures.length = j; + } } -}; -/** - * Removes all the textures within the specified displayObject and its children from the GPU - * - * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. - */ -TextureGarbageCollector.prototype.unload = function( displayObject ) -{ - var tm = this.renderer.textureManager; - - if(displayObject._texture) + /** + * Removes all the textures within the specified displayObject and its children from the GPU + * + * @param displayObject {PIXI.DisplayObject} the displayObject to remove the textures from. + */ + unload( displayObject ) { - tm.destroyTexture(displayObject._texture, true); + var tm = this.renderer.textureManager; + + if(displayObject._texture) + { + tm.destroyTexture(displayObject._texture, true); + } + + for (var i = displayObject.children.length - 1; i >= 0; i--) { + + this.unload(displayObject.children[i]); + + } } - for (var i = displayObject.children.length - 1; i >= 0; i--) { +} - this.unload(displayObject.children[i]); - - } -}; +module.exports = TextureGarbageCollector; diff --git a/src/core/renderers/webgl/TextureManager.js b/src/core/renderers/webgl/TextureManager.js index 5472d01..10e7e97 100644 --- a/src/core/renderers/webgl/TextureManager.js +++ b/src/core/renderers/webgl/TextureManager.js @@ -10,196 +10,199 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -var TextureManager = function(renderer) -{ - /** - * A reference to the current renderer - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = renderer.gl; - - /** - * Track textures in the renderer so we can no longer listen to them on destruction. - * - * @member {Array<*>} - * @private - */ - this._managedTextures = []; -}; - -TextureManager.prototype.bindTexture = function() -{ -}; - - -TextureManager.prototype.getTexture = function() -{ -}; - -/** - * Updates and/or Creates a WebGL texture for the renderer's context. - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update - */ -TextureManager.prototype.updateTexture = function(texture) -{ - texture = texture.baseTexture || texture; - - var isRenderTexture = !!texture._glRenderTargets; - - if (!texture.hasLoaded) +class TextureManager { + constructor(renderer) { - return; + /** + * A reference to the current renderer + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = renderer.gl; + + /** + * Track textures in the renderer so we can no longer listen to them on destruction. + * + * @member {Array<*>} + * @private + */ + this._managedTextures = []; } - var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!glTexture) + bindTexture() { - if(isRenderTexture) + } + + + getTexture() + { + } + + /** + * Updates and/or Creates a WebGL texture for the renderer's context. + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to update + */ + updateTexture(texture) + { + texture = texture.baseTexture || texture; + + var isRenderTexture = !!texture._glRenderTargets; + + if (!texture.hasLoaded) { - var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); - renderTarget.resize(texture.width, texture.height); - texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; - glTexture = renderTarget.texture; - } - else - { - glTexture = new GLTexture(this.gl); - glTexture.premultiplyAlpha = true; - glTexture.upload(texture.source); + return; } - texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + var glTexture = texture._glTextures[this.renderer.CONTEXT_UID]; - texture.on('update', this.updateTexture, this); - texture.on('dispose', this.destroyTexture, this); - - this._managedTextures.push(texture); - - if(texture.isPowerOfTwo) + if (!glTexture) { - if(texture.mipmap) + if(isRenderTexture) { - glTexture.enableMipmap(); - } - - if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) - { - glTexture.enableWrapClamp(); - } - else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) - { - glTexture.enableWrapRepeat(); + var renderTarget = new RenderTarget(this.gl, texture.width, texture.height, texture.scaleMode, texture.resolution); + renderTarget.resize(texture.width, texture.height); + texture._glRenderTargets[this.renderer.CONTEXT_UID] = renderTarget; + glTexture = renderTarget.texture; } else { - glTexture.enableWrapMirrorRepeat(); + glTexture = new GLTexture(this.gl); + glTexture.premultiplyAlpha = true; + glTexture.upload(texture.source); + } + + texture._glTextures[this.renderer.CONTEXT_UID] = glTexture; + + texture.on('update', this.updateTexture, this); + texture.on('dispose', this.destroyTexture, this); + + this._managedTextures.push(texture); + + if(texture.isPowerOfTwo) + { + if(texture.mipmap) + { + glTexture.enableMipmap(); + } + + if(texture.wrapMode === CONST.WRAP_MODES.CLAMP) + { + glTexture.enableWrapClamp(); + } + else if(texture.wrapMode === CONST.WRAP_MODES.REPEAT) + { + glTexture.enableWrapRepeat(); + } + else + { + glTexture.enableWrapMirrorRepeat(); + } + } + else + { + glTexture.enableWrapClamp(); + } + + if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) + { + glTexture.enableNearestScaling(); + } + else + { + glTexture.enableLinearScaling(); } } else { - glTexture.enableWrapClamp(); - } - - if(texture.scaleMode === CONST.SCALE_MODES.NEAREST) - { - glTexture.enableNearestScaling(); - } - else - { - glTexture.enableLinearScaling(); - } - } - else - { - // the textur ealrady exists so we only need to update it.. - if(isRenderTexture) - { - texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); - } - else - { - glTexture.upload(texture.source); - } - } - - return glTexture; -}; - -/** - * Deletes the texture from WebGL - * - * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy - * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. - */ -TextureManager.prototype.destroyTexture = function(texture, skipRemove) -{ - texture = texture.baseTexture || texture; - - if (!texture.hasLoaded) - { - return; - } - - if (texture._glTextures[this.renderer.CONTEXT_UID]) - { - texture._glTextures[this.renderer.CONTEXT_UID].destroy(); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); - - - delete texture._glTextures[this.renderer.CONTEXT_UID]; - - if (!skipRemove) - { - var i = this._managedTextures.indexOf(texture); - if (i !== -1) { - utils.removeItems(this._managedTextures, i, 1); + // the textur ealrady exists so we only need to update it.. + if(isRenderTexture) + { + texture._glRenderTargets[this.renderer.CONTEXT_UID].resize(texture.width, texture.height); + } + else + { + glTexture.upload(texture.source); } } - } -}; -/** - * Deletes all the textures from WebGL - */ -TextureManager.prototype.removeAll = function() -{ - // empty all the old gl textures as they are useless now - for (var i = 0; i < this._managedTextures.length; ++i) + return glTexture; + } + + /** + * Deletes the texture from WebGL + * + * @param texture {PIXI.BaseTexture|PIXI.Texture} the texture to destroy + * @param [skipRemove=false] {boolean} Whether to skip removing the texture from the TextureManager. + */ + destroyTexture(texture, skipRemove) { - var texture = this._managedTextures[i]; + texture = texture.baseTexture || texture; + + if (!texture.hasLoaded) + { + return; + } + if (texture._glTextures[this.renderer.CONTEXT_UID]) { + texture._glTextures[this.renderer.CONTEXT_UID].destroy(); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + + delete texture._glTextures[this.renderer.CONTEXT_UID]; + + if (!skipRemove) + { + var i = this._managedTextures.indexOf(texture); + if (i !== -1) { + utils.removeItems(this._managedTextures, i, 1); + } + } } } -}; -/** - * Destroys this manager and removes all its textures - */ -TextureManager.prototype.destroy = function() -{ - // destroy managed textures - for (var i = 0; i < this._managedTextures.length; ++i) + /** + * Deletes all the textures from WebGL + */ + removeAll() { - var texture = this._managedTextures[i]; - this.destroyTexture(texture, true); - texture.off('update', this.updateTexture, this); - texture.off('dispose', this.destroyTexture, this); + // empty all the old gl textures as they are useless now + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + if (texture._glTextures[this.renderer.CONTEXT_UID]) + { + delete texture._glTextures[this.renderer.CONTEXT_UID]; + } + } } - this._managedTextures = null; -}; + /** + * Destroys this manager and removes all its textures + */ + destroy() + { + // destroy managed textures + for (var i = 0; i < this._managedTextures.length; ++i) + { + var texture = this._managedTextures[i]; + this.destroyTexture(texture, true); + texture.off('update', this.updateTexture, this); + texture.off('dispose', this.destroyTexture, this); + } + + this._managedTextures = null; + } + +} module.exports = TextureManager; diff --git a/src/core/renderers/webgl/WebGLRenderer.js b/src/core/renderers/webgl/WebGLRenderer.js index bb9c439..b697701 100644 --- a/src/core/renderers/webgl/WebGLRenderer.js +++ b/src/core/renderers/webgl/WebGLRenderer.js @@ -40,523 +40,522 @@ * you need to call toDataUrl on the webgl context. * @param [options.roundPixels=false] {boolean} If true Pixi will Math.floor() x/y values when rendering, stopping pixel interpolation. */ -function WebGLRenderer(width, height, options) -{ - options = options || {}; +class WebGLRenderer extends SystemRenderer { + constructor(width, height, options) + { + options = options || {}; - SystemRenderer.call(this, 'WebGL', width, height, options); - /** - * The type of this renderer as a standardised const - * - * @member {number} - * @see PIXI.RENDERER_TYPE - */ - this.type = CONST.RENDERER_TYPE.WEBGL; + super('WebGL', width, height, options); + /** + * The type of this renderer as a standardised const + * + * @member {number} + * @see PIXI.RENDERER_TYPE + */ + this.type = CONST.RENDERER_TYPE.WEBGL; - this.handleContextLost = this.handleContextLost.bind(this); - this.handleContextRestored = this.handleContextRestored.bind(this); + this.handleContextLost = this.handleContextLost.bind(this); + this.handleContextRestored = this.handleContextRestored.bind(this); - this.view.addEventListener('webglcontextlost', this.handleContextLost, false); - this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + this.view.addEventListener('webglcontextlost', this.handleContextLost, false); + this.view.addEventListener('webglcontextrestored', this.handleContextRestored, false); + + /** + * The options passed in to create a new webgl context. + * + * @member {object} + * @private + */ + this._contextOptions = { + alpha: this.transparent, + antialias: options.antialias, + premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', + stencil: true, + preserveDrawingBuffer: options.preserveDrawingBuffer + }; + + this._backgroundColorRgba[3] = this.transparent ? 0 : 1; + + /** + * Manages the masks using the stencil buffer. + * + * @member {PIXI.MaskManager} + */ + this.maskManager = new MaskManager(this); + + /** + * Manages the stencil buffer. + * + * @member {PIXI.StencilManager} + */ + this.stencilManager = new StencilManager(this); + + /** + * An empty renderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.emptyRenderer = new ObjectRenderer(this); + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.ObjectRenderer} + */ + this.currentRenderer = this.emptyRenderer; + + this.initPlugins(); + + /** + * The current WebGL rendering context, it is created here + * + * @member {WebGLRenderingContext} + */ + // initialize the context so it is ready for the managers. + if(options.context) + { + // checks to see if a context is valid.. + validateContext(options.context); + } + + this.gl = options.context || createContext(this.view, this._contextOptions); + + this.CONTEXT_UID = CONTEXT_UID++; + + /** + * The currently active ObjectRenderer. + * + * @member {PIXI.WebGLState} + */ + this.state = new WebGLState(this.gl); + + this.renderingToScreen = true; + + + + this._initContext(); + + /** + * Manages the filters. + * + * @member {PIXI.FilterManager} + */ + this.filterManager = new FilterManager(this); + // map some webGL blend and drawmodes.. + this.drawModes = mapWebGLDrawModesToPixi(this.gl); + + + /** + * Holds the current shader + * + * @member {PIXI.Shader} + */ + this._activeShader = null; + + /** + * Holds the current render target + * + * @member {PIXI.RenderTarget} + */ + this._activeRenderTarget = null; + this._activeTextureLocation = 999; + this._activeTexture = null; + + this.setBlendMode(0); + + } /** - * The options passed in to create a new webgl context. + * Creates the WebGL context * - * @member {object} * @private */ - this._contextOptions = { - alpha: this.transparent, - antialias: options.antialias, - premultipliedAlpha: this.transparent && this.transparent !== 'notMultiplied', - stencil: true, - preserveDrawingBuffer: options.preserveDrawingBuffer - }; - - this._backgroundColorRgba[3] = this.transparent ? 0 : 1; - - /** - * Manages the masks using the stencil buffer. - * - * @member {PIXI.MaskManager} - */ - this.maskManager = new MaskManager(this); - - /** - * Manages the stencil buffer. - * - * @member {PIXI.StencilManager} - */ - this.stencilManager = new StencilManager(this); - - /** - * An empty renderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.emptyRenderer = new ObjectRenderer(this); - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.ObjectRenderer} - */ - this.currentRenderer = this.emptyRenderer; - - this.initPlugins(); - - /** - * The current WebGL rendering context, it is created here - * - * @member {WebGLRenderingContext} - */ - // initialize the context so it is ready for the managers. - if(options.context) + _initContext() { - // checks to see if a context is valid.. - validateContext(options.context); - } - - this.gl = options.context || createContext(this.view, this._contextOptions); - - this.CONTEXT_UID = CONTEXT_UID++; - - /** - * The currently active ObjectRenderer. - * - * @member {PIXI.WebGLState} - */ - this.state = new WebGLState(this.gl); - - this.renderingToScreen = true; - - - - this._initContext(); - - /** - * Manages the filters. - * - * @member {PIXI.FilterManager} - */ - this.filterManager = new FilterManager(this); - // map some webGL blend and drawmodes.. - this.drawModes = mapWebGLDrawModesToPixi(this.gl); - - - /** - * Holds the current shader - * - * @member {PIXI.Shader} - */ - this._activeShader = null; - - /** - * Holds the current render target - * - * @member {PIXI.RenderTarget} - */ - this._activeRenderTarget = null; - this._activeTextureLocation = 999; - this._activeTexture = null; - - this.setBlendMode(0); - - -} - -// constructor -WebGLRenderer.prototype = Object.create(SystemRenderer.prototype); -WebGLRenderer.prototype.constructor = WebGLRenderer; -module.exports = WebGLRenderer; -utils.pluginTarget.mixin(WebGLRenderer); - -/** - * Creates the WebGL context - * - * @private - */ -WebGLRenderer.prototype._initContext = function () -{ - var gl = this.gl; - - // create a texture manager... - this.textureManager = new TextureManager(this); - this.textureGC = new TextureGarbageCollector(this); - - this.state.resetToDefault(); - - this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); - this.rootRenderTarget.clearColor = this._backgroundColorRgba; - - - this.bindRenderTarget(this.rootRenderTarget); - - this.emit('context', gl); - - // setup the width/height properties and gl viewport - this.resize(this.width, this.height); -}; - -/** - * Renders the object to its webGL view - * - * @param displayObject {PIXI.DisplayObject} the object to be rendered - * @param renderTexture {PIXI.RenderTexture} - * @param [clear] {boolean} Should the canvas be cleared before the new render - * @param [transform] {PIXI.Transform} - * @param [skipUpdateTransform] {boolean} - */ -WebGLRenderer.prototype.render = function (displayObject, renderTexture, clear, transform, skipUpdateTransform) -{ - - // can be handy to know! - this.renderingToScreen = !renderTexture; - - this.emit('prerender'); - - - // no point rendering if our context has been blown up! - if (!this.gl || this.gl.isContextLost()) - { - return; - } - - if(!renderTexture) - { - this._lastObjectRendered = displayObject; - } - - if(!skipUpdateTransform) - { - // update the scene graph - var cacheParent = displayObject.parent; - displayObject.parent = this._tempDisplayObjectParent; - displayObject.updateTransform(); - displayObject.parent = cacheParent; - // displayObject.hitArea = //TODO add a temp hit area - } - - this.bindRenderTexture(renderTexture, transform); - - this.currentRenderer.start(); - - if(clear !== undefined ? clear : this.clearBeforeRender) - { - this._activeRenderTarget.clear(); - } - - displayObject.renderWebGL(this); - - // apply transform.. - this.currentRenderer.flush(); - - //this.setObjectRenderer(this.emptyRenderer); - - this.textureGC.update(); - - this.emit('postrender'); -}; - -/** - * Changes the current renderer to the one given in parameter - * - * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. - */ -WebGLRenderer.prototype.setObjectRenderer = function (objectRenderer) -{ - if (this.currentRenderer === objectRenderer) - { - return; - } - - this.currentRenderer.stop(); - this.currentRenderer = objectRenderer; - this.currentRenderer.start(); -}; - -/** - * This shoudl be called if you wish to do some custom rendering - * It will basically render anything that may be batched up such as sprites - * - */ -WebGLRenderer.prototype.flush = function () -{ - this.setObjectRenderer(this.emptyRenderer); -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param width {number} the new width of the webGL view - * @param height {number} the new height of the webGL view - */ -WebGLRenderer.prototype.resize = function (width, height) -{ - // if(width * this.resolution === this.width && height * this.resolution === this.height)return; - - SystemRenderer.prototype.resize.call(this, width, height); - - this.rootRenderTarget.resize(width, height); - - if(this._activeRenderTarget === this.rootRenderTarget) - { - this.rootRenderTarget.activate(); - - if(this._activeShader) - { - this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); - } - } -}; - -/** - * Resizes the webGL view to the specified width and height. - * - * @param blendMode {number} the desired blend mode - */ -WebGLRenderer.prototype.setBlendMode = function (blendMode) -{ - this.state.setBlendMode(blendMode); -}; - -/** - * Erases the active render target and fills the drawing area with a colour - * - * @param [clearColor] {number} The colour - */ -WebGLRenderer.prototype.clear = function (clearColor) -{ - this._activeRenderTarget.clear(clearColor); -}; - -/** - * Sets the transform of the active render target to the given matrix - * - * @param matrix {PIXI.Matrix} The transformation matrix - */ -WebGLRenderer.prototype.setTransform = function (matrix) -{ - this._activeRenderTarget.transform = matrix; -}; - - -/** - * Binds a render texture for rendering - * - * @param renderTexture {PIXI.RenderTexture} The render texture to render - * @param transform {PIXI.Transform} The transform to be applied to the render texture - */ -WebGLRenderer.prototype.bindRenderTexture = function (renderTexture, transform) -{ - var renderTarget; - - if(renderTexture) - { - var baseTexture = renderTexture.baseTexture; var gl = this.gl; - if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) - { + // create a texture manager... + this.textureManager = new TextureManager(this); + this.textureGC = new TextureGarbageCollector(this); - this.textureManager.updateTexture(baseTexture); - gl.bindTexture(gl.TEXTURE_2D, null); + this.state.resetToDefault(); + + this.rootRenderTarget = new RenderTarget(gl, this.width, this.height, null, this.resolution, true); + this.rootRenderTarget.clearColor = this._backgroundColorRgba; + + + this.bindRenderTarget(this.rootRenderTarget); + + this.emit('context', gl); + + // setup the width/height properties and gl viewport + this.resize(this.width, this.height); + } + + /** + * Renders the object to its webGL view + * + * @param displayObject {PIXI.DisplayObject} the object to be rendered + * @param renderTexture {PIXI.RenderTexture} + * @param [clear] {boolean} Should the canvas be cleared before the new render + * @param [transform] {PIXI.Transform} + * @param [skipUpdateTransform] {boolean} + */ + render(displayObject, renderTexture, clear, transform, skipUpdateTransform) + { + + // can be handy to know! + this.renderingToScreen = !renderTexture; + + this.emit('prerender'); + + + // no point rendering if our context has been blown up! + if (!this.gl || this.gl.isContextLost()) + { + return; + } + + if(!renderTexture) + { + this._lastObjectRendered = displayObject; + } + + if(!skipUpdateTransform) + { + // update the scene graph + var cacheParent = displayObject.parent; + displayObject.parent = this._tempDisplayObjectParent; + displayObject.updateTransform(); + displayObject.parent = cacheParent; + // displayObject.hitArea = //TODO add a temp hit area + } + + this.bindRenderTexture(renderTexture, transform); + + this.currentRenderer.start(); + + if(clear !== undefined ? clear : this.clearBeforeRender) + { + this._activeRenderTarget.clear(); + } + + displayObject.renderWebGL(this); + + // apply transform.. + this.currentRenderer.flush(); + + //this.setObjectRenderer(this.emptyRenderer); + + this.textureGC.update(); + + this.emit('postrender'); + } + + /** + * Changes the current renderer to the one given in parameter + * + * @param objectRenderer {PIXI.ObjectRenderer} The object renderer to use. + */ + setObjectRenderer(objectRenderer) + { + if (this.currentRenderer === objectRenderer) + { + return; + } + + this.currentRenderer.stop(); + this.currentRenderer = objectRenderer; + this.currentRenderer.start(); + } + + /** + * This shoudl be called if you wish to do some custom rendering + * It will basically render anything that may be batched up such as sprites + * + */ + flush() + { + this.setObjectRenderer(this.emptyRenderer); + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param width {number} the new width of the webGL view + * @param height {number} the new height of the webGL view + */ + resize(width, height) + { + // if(width * this.resolution === this.width && height * this.resolution === this.height)return; + + SystemRenderer.prototype.resize.call(this, width, height); + + this.rootRenderTarget.resize(width, height); + + if(this._activeRenderTarget === this.rootRenderTarget) + { + this.rootRenderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = this.rootRenderTarget.projectionMatrix.toArray(true); + } + } + } + + /** + * Resizes the webGL view to the specified width and height. + * + * @param blendMode {number} the desired blend mode + */ + setBlendMode(blendMode) + { + this.state.setBlendMode(blendMode); + } + + /** + * Erases the active render target and fills the drawing area with a colour + * + * @param [clearColor] {number} The colour + */ + clear(clearColor) + { + this._activeRenderTarget.clear(clearColor); + } + + /** + * Sets the transform of the active render target to the given matrix + * + * @param matrix {PIXI.Matrix} The transformation matrix + */ + setTransform(matrix) + { + this._activeRenderTarget.transform = matrix; + } + + + /** + * Binds a render texture for rendering + * + * @param renderTexture {PIXI.RenderTexture} The render texture to render + * @param transform {PIXI.Transform} The transform to be applied to the render texture + */ + bindRenderTexture(renderTexture, transform) + { + var renderTarget; + + if(renderTexture) + { + var baseTexture = renderTexture.baseTexture; + var gl = this.gl; + + if(!baseTexture._glRenderTargets[this.CONTEXT_UID]) + { + + this.textureManager.updateTexture(baseTexture); + gl.bindTexture(gl.TEXTURE_2D, null); + } + else + { + // the texture needs to be unbound if its being rendererd too.. + this._activeTextureLocation = baseTexture._id; + gl.activeTexture(gl.TEXTURE0 + baseTexture._id); + gl.bindTexture(gl.TEXTURE_2D, null); + } + + + renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; + renderTarget.setFrame(renderTexture.frame); } else { - // the texture needs to be unbound if its being rendererd too.. - this._activeTextureLocation = baseTexture._id; - gl.activeTexture(gl.TEXTURE0 + baseTexture._id); - gl.bindTexture(gl.TEXTURE_2D, null); + renderTarget = this.rootRenderTarget; } + renderTarget.transform = transform; + this.bindRenderTarget(renderTarget); - renderTarget = baseTexture._glRenderTargets[this.CONTEXT_UID]; - renderTarget.setFrame(renderTexture.frame); - } - else - { - renderTarget = this.rootRenderTarget; + return this; } - renderTarget.transform = transform; - this.bindRenderTarget(renderTarget); - - return this; -}; - -/** - * Changes the current render target to the one given in parameter - * - * @param renderTarget {PIXI.RenderTarget} the new render target - */ -WebGLRenderer.prototype.bindRenderTarget = function (renderTarget) -{ - if(renderTarget !== this._activeRenderTarget) + /** + * Changes the current render target to the one given in parameter + * + * @param renderTarget {PIXI.RenderTarget} the new render target + */ + bindRenderTarget(renderTarget) { - this._activeRenderTarget = renderTarget; - renderTarget.activate(); - - if(this._activeShader) + if(renderTarget !== this._activeRenderTarget) { - this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + this._activeRenderTarget = renderTarget; + renderTarget.activate(); + + if(this._activeShader) + { + this._activeShader.uniforms.projectionMatrix = renderTarget.projectionMatrix.toArray(true); + } + + + this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); } - - this.stencilManager.setMaskStack( renderTarget.stencilMaskStack ); + return this; } - return this; -}; - -/** - * Changes the current shader to the one given in parameter - * - * @param shader {PIXI.Shader} the new shader - */ -WebGLRenderer.prototype.bindShader = function (shader) -{ - //TODO cache - if(this._activeShader !== shader) + /** + * Changes the current shader to the one given in parameter + * + * @param shader {PIXI.Shader} the new shader + */ + bindShader(shader) { - this._activeShader = shader; - shader.bind(); + //TODO cache + if(this._activeShader !== shader) + { + this._activeShader = shader; + shader.bind(); - // automatically set the projection matrix - shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + // automatically set the projection matrix + shader.uniforms.projectionMatrix = this._activeRenderTarget.projectionMatrix.toArray(true); + } + + return this; } - return this; -}; - -/** - * Binds the texture ... @mat - * - * @param texture {PIXI.Texture} the new texture - * @param location {number} the texture location - */ -WebGLRenderer.prototype.bindTexture = function (texture, location) -{ - texture = texture.baseTexture || texture; - - var gl = this.gl; - - //TODO test perf of cache? - location = location || 0; - - if(this._activeTextureLocation !== location)// + /** + * Binds the texture ... @mat + * + * @param texture {PIXI.Texture} the new texture + * @param location {number} the texture location + */ + bindTexture(texture, location) { - this._activeTextureLocation = location; - gl.activeTexture(gl.TEXTURE0 + location ); + texture = texture.baseTexture || texture; + + var gl = this.gl; + + //TODO test perf of cache? + location = location || 0; + + if(this._activeTextureLocation !== location)// + { + this._activeTextureLocation = location; + gl.activeTexture(gl.TEXTURE0 + location ); + } + + //TODO - can we cache this texture too? + this._activeTexture = texture; + + if (!texture._glTextures[this.CONTEXT_UID]) + { + // this will also bind the texture.. + this.textureManager.updateTexture(texture); + + } + else + { + texture.touched = this.textureGC.count; + // bind the current texture + texture._glTextures[this.CONTEXT_UID].bind(); + } + + return this; } - //TODO - can we cache this texture too? - this._activeTexture = texture; - - if (!texture._glTextures[this.CONTEXT_UID]) + createVao() { - // this will also bind the texture.. - this.textureManager.updateTexture(texture); - + return new glCore.VertexArrayObject(this.gl, this.state.attribState); } - else + + /** + * Resets the WebGL state so you can render things however you fancy! + */ + reset() { - texture.touched = this.textureGC.count; - // bind the current texture - texture._glTextures[this.CONTEXT_UID].bind(); + this.setObjectRenderer(this.emptyRenderer); + + this._activeShader = null; + this._activeRenderTarget = this.rootRenderTarget; + this._activeTextureLocation = 999; + this._activeTexture = null; + + // bind the main frame buffer (the screen); + this.rootRenderTarget.activate(); + + this.state.resetToDefault(); + + return this; } - return this; -}; - -WebGLRenderer.prototype.createVao = function () -{ - return new glCore.VertexArrayObject(this.gl, this.state.attribState); -}; - -/** - * Resets the WebGL state so you can render things however you fancy! - */ -WebGLRenderer.prototype.reset = function () -{ - this.setObjectRenderer(this.emptyRenderer); - - this._activeShader = null; - this._activeRenderTarget = this.rootRenderTarget; - this._activeTextureLocation = 999; - this._activeTexture = null; - - // bind the main frame buffer (the screen); - this.rootRenderTarget.activate(); - - this.state.resetToDefault(); - - return this; -}; - -/** - * Handles a lost webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextLost = function (event) -{ - event.preventDefault(); -}; - -/** - * Handles a restored webgl context - * - * @private - */ -WebGLRenderer.prototype.handleContextRestored = function () -{ - this._initContext(); - this.textureManager.removeAll(); -}; - -/** - * Removes everything from the renderer (event listeners, spritebatch, etc...) - * - * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 - */ -WebGLRenderer.prototype.destroy = function (removeView) -{ - this.destroyPlugins(); - - // remove listeners - this.view.removeEventListener('webglcontextlost', this.handleContextLost); - this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); - - this.textureManager.destroy(); - - // call base destroy - SystemRenderer.prototype.destroy.call(this, removeView); - - this.uid = 0; - - // destroy the managers - this.maskManager.destroy(); - this.stencilManager.destroy(); - this.filterManager.destroy(); - - this.maskManager = null; - this.filterManager = null; - this.textureManager = null; - this.currentRenderer = null; - - this.handleContextLost = null; - this.handleContextRestored = null; - - this._contextOptions = null; - this.gl.useProgram(null); - - if(this.gl.getExtension('WEBGL_lose_context')) + /** + * Handles a lost webgl context + * + * @private + */ + handleContextLost(event) { - this.gl.getExtension('WEBGL_lose_context').loseContext(); + event.preventDefault(); } - this.gl = null; + /** + * Handles a restored webgl context + * + * @private + */ + handleContextRestored() + { + this._initContext(); + this.textureManager.removeAll(); + } - // this = null; -}; + /** + * Removes everything from the renderer (event listeners, spritebatch, etc...) + * + * @param [removeView=false] {boolean} Removes the Canvas element from the DOM. https://github.com/pixijs/pixi.js/issues/2233 + */ + destroy(removeView) + { + this.destroyPlugins(); + + // remove listeners + this.view.removeEventListener('webglcontextlost', this.handleContextLost); + this.view.removeEventListener('webglcontextrestored', this.handleContextRestored); + + this.textureManager.destroy(); + + // call base destroy + super.destroy(removeView); + + this.uid = 0; + + // destroy the managers + this.maskManager.destroy(); + this.stencilManager.destroy(); + this.filterManager.destroy(); + + this.maskManager = null; + this.filterManager = null; + this.textureManager = null; + this.currentRenderer = null; + + this.handleContextLost = null; + this.handleContextRestored = null; + + this._contextOptions = null; + this.gl.useProgram(null); + + if(this.gl.getExtension('WEBGL_lose_context')) + { + this.gl.getExtension('WEBGL_lose_context').loseContext(); + } + + this.gl = null; + + // this = null; + } + +} + +module.exports = WebGLRenderer; +utils.pluginTarget.mixin(WebGLRenderer); diff --git a/src/core/renderers/webgl/WebGLState.js b/src/core/renderers/webgl/WebGLState.js index d4fa477..696b41f 100755 --- a/src/core/renderers/webgl/WebGLState.js +++ b/src/core/renderers/webgl/WebGLState.js @@ -1,90 +1,5 @@ var mapWebGLBlendModesToPixi = require('./utils/mapWebGLBlendModesToPixi'); -/** - * A WebGL state machines - * - * @memberof PIXI - * @class - * @param gl {WebGLRenderingContext} The current WebGL rendering context - */ -function WebGLState(gl) -{ - /** - * The current active state - * - * @member {Uint8Array} - */ - this.activeState = new Uint8Array(16); - - /** - * The default state - * - * @member {Uint8Array} - */ - this.defaultState = new Uint8Array(16); - - // default blend mode.. - this.defaultState[0] = 1; - - /** - * The current state index in the stack - * - * @member {number} - * @private - */ - this.stackIndex = 0; - - /** - * The stack holding all the different states - * - * @member {Array<*>} - * @private - */ - this.stack = []; - - /** - * The current WebGL rendering context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); - - this.attribState = {tempAttribState:new Array(this.maxAttribs), - attribState:new Array(this.maxAttribs)}; - - this.blendModes = mapWebGLBlendModesToPixi(gl); - - // check we have vao.. - this.nativeVaoExtension = ( - gl.getExtension('OES_vertex_array_object') || - gl.getExtension('MOZ_OES_vertex_array_object') || - gl.getExtension('WEBKIT_OES_vertex_array_object') - ); -} - -/** - * Pushes a new active state - */ -WebGLState.prototype.push = function() -{ - // next state.. - var state = this.stack[++this.stackIndex]; - - if(!state) - { - state = this.stack[this.stackIndex] = new Uint8Array(16); - } - - // copy state.. - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) - { - this.activeState[i] = state[i]; - } -}; - var BLEND = 0, DEPTH_TEST = 1, FRONT_FACE = 2, @@ -92,190 +7,278 @@ BLEND_FUNC = 4; /** - * Pops a state out + * A WebGL state machines + * + * @memberof PIXI + * @class + * @param gl {WebGLRenderingContext} The current WebGL rendering context */ -WebGLState.prototype.pop = function() -{ - var state = this.stack[--this.stackIndex]; - this.setState(state); -}; - -/** - * Sets the current state - * @param state {number} - */ -WebGLState.prototype.setState = function(state) -{ - this.setBlend(state[BLEND]); - this.setDepthTest(state[DEPTH_TEST]); - this.setFrontFace(state[FRONT_FACE]); - this.setCullFace(state[CULL_FACE]); - this.setBlendMode(state[BLEND_FUNC]); -}; - -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlend = function(value) -{ - if(this.activeState[BLEND] === value|0) { - return; - } - - this.activeState[BLEND] = value|0; - - var gl = this.gl; - - if(value) +class WebGLState { + constructor(gl) { - gl.enable(gl.BLEND); + /** + * The current active state + * + * @member {Uint8Array} + */ + this.activeState = new Uint8Array(16); + + /** + * The default state + * + * @member {Uint8Array} + */ + this.defaultState = new Uint8Array(16); + + // default blend mode.. + this.defaultState[0] = 1; + + /** + * The current state index in the stack + * + * @member {number} + * @private + */ + this.stackIndex = 0; + + /** + * The stack holding all the different states + * + * @member {Array<*>} + * @private + */ + this.stack = []; + + /** + * The current WebGL rendering context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + this.maxAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS); + + this.attribState = {tempAttribState:new Array(this.maxAttribs), + attribState:new Array(this.maxAttribs)}; + + this.blendModes = mapWebGLBlendModesToPixi(gl); + + // check we have vao.. + this.nativeVaoExtension = ( + gl.getExtension('OES_vertex_array_object') || + gl.getExtension('MOZ_OES_vertex_array_object') || + gl.getExtension('WEBKIT_OES_vertex_array_object') + ); } - else + + /** + * Pushes a new active state + */ + push() { - gl.disable(gl.BLEND); - } -}; + // next state.. + var state = this.stack[++this.stackIndex]; -/** - * Sets the blend mode ? @mat - * @param value {number} - */ -WebGLState.prototype.setBlendMode = function(value) -{ - if(value === this.activeState[BLEND_FUNC]) { - return; + if(!state) + { + state = this.stack[this.stackIndex] = new Uint8Array(16); + } + + // copy state.. + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = state[i]; + } } - this.activeState[BLEND_FUNC] = value; - - this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setDepthTest = function(value) -{ - if(this.activeState[DEPTH_TEST] === value|0) { - return; - } - - this.activeState[DEPTH_TEST] = value|0; - - var gl = this.gl; - - if(value) + /** + * Pops a state out + */ + pop() { - gl.enable(gl.DEPTH_TEST); + var state = this.stack[--this.stackIndex]; + this.setState(state); } - else + + /** + * Sets the current state + * @param state {number} + */ + setState(state) { - gl.disable(gl.DEPTH_TEST); - } -}; - -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setCullFace = function(value) -{ - if(this.activeState[CULL_FACE] === value|0) { - return; + this.setBlend(state[BLEND]); + this.setDepthTest(state[DEPTH_TEST]); + this.setFrontFace(state[FRONT_FACE]); + this.setCullFace(state[CULL_FACE]); + this.setBlendMode(state[BLEND_FUNC]); } - this.activeState[CULL_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlend(value) { - gl.enable(gl.CULL_FACE); + if(this.activeState[BLEND] === value|0) { + return; + } + + this.activeState[BLEND] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.BLEND); + } + else + { + gl.disable(gl.BLEND); + } } - else + + /** + * Sets the blend mode ? @mat + * @param value {number} + */ + setBlendMode(value) { - gl.disable(gl.CULL_FACE); - } -}; + if(value === this.activeState[BLEND_FUNC]) { + return; + } -/** - * Sets the depth test @mat - * @param value {number} - */ -WebGLState.prototype.setFrontFace = function(value) -{ - if(this.activeState[FRONT_FACE] === value|0) { - return; + this.activeState[BLEND_FUNC] = value; + + this.gl.blendFunc(this.blendModes[value][0], this.blendModes[value][1]); } - this.activeState[FRONT_FACE] = value|0; - - var gl = this.gl; - - if(value) + /** + * Sets the depth test @mat + * @param value {number} + */ + setDepthTest(value) { - gl.frontFace(gl.CW); + if(this.activeState[DEPTH_TEST] === value|0) { + return; + } + + this.activeState[DEPTH_TEST] = value|0; + + var gl = this.gl; + + if(value) + { + gl.enable(gl.DEPTH_TEST); + } + else + { + gl.disable(gl.DEPTH_TEST); + } } - else + + /** + * Sets the depth test @mat + * @param value {number} + */ + setCullFace(value) { - gl.frontFace(gl.CCW); - } -}; + if(this.activeState[CULL_FACE] === value|0) { + return; + } -/** - * Disables all the vaos in use - */ -WebGLState.prototype.resetAttributes = function() -{ - var i; + this.activeState[CULL_FACE] = value|0; - for ( i = 0; i < this.attribState.tempAttribState.length; i++) { - this.attribState.tempAttribState[i] = 0; + var gl = this.gl; + + if(value) + { + gl.enable(gl.CULL_FACE); + } + else + { + gl.disable(gl.CULL_FACE); + } } - for ( i = 0; i < this.attribState.attribState.length; i++) { - this.attribState.attribState[i] = 0; - } - - var gl = this.gl; - - // im going to assume one is always active for performance reasons. - for (i = 1; i < this.maxAttribs; i++) + /** + * Sets the depth test @mat + * @param value {number} + */ + setFrontFace(value) { - gl.disableVertexAttribArray(i); + if(this.activeState[FRONT_FACE] === value|0) { + return; + } + + this.activeState[FRONT_FACE] = value|0; + + var gl = this.gl; + + if(value) + { + gl.frontFace(gl.CW); + } + else + { + gl.frontFace(gl.CCW); + } } -}; -//used -/** - * Resets all the logic and disables the vaos - */ -WebGLState.prototype.resetToDefault = function() -{ - - // unbind any VAO if they exist.. - if(this.nativeVaoExtension) + /** + * Disables all the vaos in use + */ + resetAttributes() { - this.nativeVaoExtension.bindVertexArrayOES(null); + var i; + + for ( i = 0; i < this.attribState.tempAttribState.length; i++) { + this.attribState.tempAttribState[i] = 0; + } + + for ( i = 0; i < this.attribState.attribState.length; i++) { + this.attribState.attribState[i] = 0; + } + + var gl = this.gl; + + // im going to assume one is always active for performance reasons. + for (i = 1; i < this.maxAttribs; i++) + { + gl.disableVertexAttribArray(i); + } } - - // reset all attributs.. - this.resetAttributes(); - - // set active state so we can force overrides of gl state - for (var i = 0; i < this.activeState.length; i++) + //used + /** + * Resets all the logic and disables the vaos + */ + resetToDefault() { - this.activeState[i] = 32; + + // unbind any VAO if they exist.. + if(this.nativeVaoExtension) + { + this.nativeVaoExtension.bindVertexArrayOES(null); + } + + + // reset all attributs.. + this.resetAttributes(); + + // set active state so we can force overrides of gl state + for (var i = 0; i < this.activeState.length; i++) + { + this.activeState[i] = 32; + } + + var gl = this.gl; + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); + + + this.setState(this.defaultState); } - var gl = this.gl; - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); - - - this.setState(this.defaultState); -}; +} module.exports = WebGLState; diff --git a/src/core/renderers/webgl/filters/Filter.js b/src/core/renderers/webgl/filters/Filter.js index cb90ed4..1f81938 100644 --- a/src/core/renderers/webgl/filters/Filter.js +++ b/src/core/renderers/webgl/filters/Filter.js @@ -12,134 +12,139 @@ * @param [uniforms] {object} Custom uniforms to use to augment the built-in ones. * @param [fragmentSrc] {string} The source of the fragment shader. */ -function Filter(vertexSrc, fragmentSrc, uniforms) -{ - - /** - * The vertex shader. - * - * @member {string} - */ - this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; - - /** - * The fragment shader. - * - * @member {string} - */ - this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; - - this.blendMode = CONST.BLEND_MODES.NORMAL; - - // pull out the vertex and shader uniforms if they are not specified.. - // currently this does not extract structs only default types - this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); - - this.uniforms = {}; - - for (var i in this.uniformData) +class Filter { + constructor(vertexSrc, fragmentSrc, uniforms) { - this.uniforms[i] = this.uniformData[i].value; + + /** + * The vertex shader. + * + * @member {string} + */ + this.vertexSrc = vertexSrc || Filter.defaultVertexSrc; + + /** + * The fragment shader. + * + * @member {string} + */ + this.fragmentSrc = fragmentSrc || Filter.defaultFragmentSrc; + + this.blendMode = CONST.BLEND_MODES.NORMAL; + + // pull out the vertex and shader uniforms if they are not specified.. + // currently this does not extract structs only default types + this.uniformData = uniforms || extractUniformsFromSrc( this.vertexSrc, this.fragmentSrc, 'projectionMatrix|uSampler'); + + this.uniforms = {}; + + for (var i in this.uniformData) + { + this.uniforms[i] = this.uniformData[i].value; + } + + // this is where we store shader references.. + // TODO we could cache this! + this.glShaders = []; + + // used for cacheing.. sure there is a better way! + if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + { + SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + } + + this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + + /** + * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + */ + this.padding = 4; + + /** + * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. + * @member {number} + */ + this.resolution = 1; + + /** + * If enabled is true the filter is applied, if false it will not. + * @member {boolean} + */ + this.enabled = true; } - // this is where we store shader references.. - // TODO we could cache this! - this.glShaders = []; + // var tempMatrix = new math.Matrix(); - // used for cacheing.. sure there is a better way! - if(!SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]) + apply(filterManager, input, output, clear) { - SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc] = utils.uid(); + // --- // + // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); + + // do as you please! + + filterManager.applyFilter(this, input, output, clear); + + // or just do a regular render.. } - this.glShaderKey = SOURCE_KEY_MAP[this.vertexSrc + this.fragmentSrc]; + /** + * The default vertex shader source + * + * @static + * @constant + */ + static get defaultVertexSrc() { + return [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + + 'uniform mat3 projectionMatrix;', + 'uniform mat3 filterMatrix;', + + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', + + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', + ' vTextureCoord = aTextureCoord ;', + '}' + ].join('\n'); + } /** - * The padding of the filter. Some filters require extra space to breath such as a blur. Increasing this will add extra width and height to the bounds of the object that the filter is applied to. + * The default fragment shader source + * + * @static + * @constant */ - this.padding = 4; + static get defaultFragmentSrc() { + return [ + 'varying vec2 vTextureCoord;', + 'varying vec2 vFilterCoord;', - /** - * The resolution of the filter. Setting this to be lower will lower the quality but increase the performance of the filter. - * @member {number} - */ - this.resolution = 1; + 'uniform sampler2D uSampler;', + 'uniform sampler2D filterSampler;', - /** - * If enabled is true the filter is applied, if false it will not. - * @member {boolean} - */ - this.enabled = true; + 'void main(void){', + ' vec4 masky = texture2D(filterSampler, vFilterCoord);', + ' vec4 sample = texture2D(uSampler, vTextureCoord);', + ' vec4 color;', + ' if(mod(vFilterCoord.x, 1.0) > 0.5)', + ' {', + ' color = vec4(1.0, 0.0, 0.0, 1.0);', + ' }', + ' else', + ' {', + ' color = vec4(0.0, 1.0, 0.0, 1.0);', + ' }', + // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', + ' gl_FragColor = mix(sample, masky, 0.5);', + ' gl_FragColor *= sample.a;', + '}' + ].join('\n'); + } + } -// constructor -//Filter.prototype.constructor = Filter; module.exports = Filter; - -// var tempMatrix = new math.Matrix(); - -Filter.prototype.apply = function(filterManager, input, output, clear) -{ - // --- // - // this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(tempMatrix, window.panda ); - - // do as you please! - - filterManager.applyFilter(this, input, output, clear); - - // or just do a regular render.. -}; - -/** - * The default vertex shader source - * - * @static - * @constant - */ -Filter.defaultVertexSrc = [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - - 'uniform mat3 projectionMatrix;', - 'uniform mat3 filterMatrix;', - - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vFilterCoord = ( filterMatrix * vec3( aTextureCoord, 1.0) ).xy;', - ' vTextureCoord = aTextureCoord ;', - '}' -].join('\n'); - -/** - * The default fragment shader source - * - * @static - * @constant - */ -Filter.defaultFragmentSrc = [ - 'varying vec2 vTextureCoord;', - 'varying vec2 vFilterCoord;', - - 'uniform sampler2D uSampler;', - 'uniform sampler2D filterSampler;', - - 'void main(void){', - ' vec4 masky = texture2D(filterSampler, vFilterCoord);', - ' vec4 sample = texture2D(uSampler, vTextureCoord);', - ' vec4 color;', - ' if(mod(vFilterCoord.x, 1.0) > 0.5)', - ' {', - ' color = vec4(1.0, 0.0, 0.0, 1.0);', - ' }', - ' else', - ' {', - ' color = vec4(0.0, 1.0, 0.0, 1.0);', - ' }', - // ' gl_FragColor = vec4(mod(vFilterCoord.x, 1.5), vFilterCoord.y,0.0,1.0);', - ' gl_FragColor = mix(sample, masky, 0.5);', - ' gl_FragColor *= sample.a;', - '}' -].join('\n'); diff --git a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js index 7f5f0fd..7079fcb 100644 --- a/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js +++ b/src/core/renderers/webgl/filters/spriteMask/SpriteMaskFilter.js @@ -11,39 +11,40 @@ * @memberof PIXI * @param sprite {PIXI.Sprite} the target sprite */ -function SpriteMaskFilter(sprite) -{ - var maskMatrix = new math.Matrix(); +class SpriteMaskFilter extends Filter { + constructor(sprite) + { + var maskMatrix = new math.Matrix(); - Filter.call(this, - glslify('./spriteMaskFilter.vert'), - glslify('./spriteMaskFilter.frag') - ); + super( + glslify('./spriteMaskFilter.vert'), + glslify('./spriteMaskFilter.frag') + ); - sprite.renderable = false; + sprite.renderable = false; - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + } + + /** + * Applies the filter + * + * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from + * @param input {PIXI.RenderTarget} + * @param output {PIXI.RenderTarget} + */ + apply(filterManager, input, output) + { + var maskSprite = this.maskSprite; + + this.uniforms.mask = maskSprite._texture; + this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); + this.uniforms.alpha = maskSprite.worldAlpha; + + filterManager.applyFilter(this, input, output); + } + } -SpriteMaskFilter.prototype = Object.create(Filter.prototype); -SpriteMaskFilter.prototype.constructor = SpriteMaskFilter; module.exports = SpriteMaskFilter; - -/** - * Applies the filter - * - * @param filterManager {PIXI.FilterManager} The renderer to retrieve the filter from - * @param input {PIXI.RenderTarget} - * @param output {PIXI.RenderTarget} - */ -SpriteMaskFilter.prototype.apply = function (filterManager, input, output) -{ - var maskSprite = this.maskSprite; - - this.uniforms.mask = maskSprite._texture; - this.uniforms.otherMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, maskSprite ); - this.uniforms.alpha = maskSprite.worldAlpha; - - filterManager.applyFilter(this, input, output); -}; diff --git a/src/core/renderers/webgl/managers/BlendModeManager.js b/src/core/renderers/webgl/managers/BlendModeManager.js index 5c9eca9..d3b2113 100644 --- a/src/core/renderers/webgl/managers/BlendModeManager.js +++ b/src/core/renderers/webgl/managers/BlendModeManager.js @@ -6,37 +6,38 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function BlendModeManager(renderer) -{ - WebGLManager.call(this, renderer); - - /** - * @member {number} - */ - this.currentBlendMode = 99999; -} - -BlendModeManager.prototype = Object.create(WebGLManager.prototype); -BlendModeManager.prototype.constructor = BlendModeManager; -module.exports = BlendModeManager; - -/** - * Sets-up the given blendMode from WebGL's point of view. - * - * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See - * {@link PIXI.BLEND_MODES} for possible values. - */ -BlendModeManager.prototype.setBlendMode = function (blendMode) -{ - if (this.currentBlendMode === blendMode) +class BlendModeManager extends WebGLManager { + constructor(renderer) { - return false; + super(renderer); + + /** + * @member {number} + */ + this.currentBlendMode = 99999; } - this.currentBlendMode = blendMode; + /** + * Sets-up the given blendMode from WebGL's point of view. + * + * @param blendMode {number} the blendMode, should be a Pixi const, such as `PIXI.BLEND_MODES.ADD`. See + * {@link PIXI.BLEND_MODES} for possible values. + */ + setBlendMode(blendMode) + { + if (this.currentBlendMode === blendMode) + { + return false; + } - var mode = this.renderer.blendModes[this.currentBlendMode]; - this.renderer.gl.blendFunc(mode[0], mode[1]); + this.currentBlendMode = blendMode; - return true; -}; + var mode = this.renderer.blendModes[this.currentBlendMode]; + this.renderer.gl.blendFunc(mode[0], mode[1]); + + return true; + } + +} + +module.exports = BlendModeManager; diff --git a/src/core/renderers/webgl/managers/FilterManager.js b/src/core/renderers/webgl/managers/FilterManager.js index 9d21162..873a2dd 100644 --- a/src/core/renderers/webgl/managers/FilterManager.js +++ b/src/core/renderers/webgl/managers/FilterManager.js @@ -7,16 +7,17 @@ filterTransforms = require('../filters/filterTransforms'), bitTwiddle = require('bit-twiddle'); -var FilterState = function() -{ - this.renderTarget = null; - this.sourceFrame = new math.Rectangle(); - this.destinationFrame = new math.Rectangle(); - this.filters = []; - this.target = null; - this.resolution = 1; -}; - +class FilterState { + constructor() + { + this.renderTarget = null; + this.sourceFrame = new math.Rectangle(); + this.destinationFrame = new math.Rectangle(); + this.filters = []; + this.target = null; + this.resolution = 1; + } +} /** * @class @@ -24,416 +25,417 @@ * @extends PIXI.WebGLManager * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function FilterManager(renderer) -{ - WebGLManager.call(this, renderer); - - this.gl = this.renderer.gl; - // know about sprites! - this.quad = new Quad(this.gl, renderer.state.attribState); - - this.shaderCache = {}; - // todo add default! - this.pool = {}; - - this.filterData = null; -} - -FilterManager.prototype = Object.create(WebGLManager.prototype); -FilterManager.prototype.constructor = FilterManager; -module.exports = FilterManager; - -FilterManager.prototype.pushFilter = function(target, filters) -{ - var renderer = this.renderer; - - var filterData = this.filterData; - - if(!filterData) +class FilterManager extends WebGLManager { + constructor(renderer) { - filterData = this.renderer._activeRenderTarget.filterStack; + super(renderer); - // add new stack - var filterState = new FilterState(); - filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; - filterState.renderTarget = renderer._activeRenderTarget; + this.gl = this.renderer.gl; + // know about sprites! + this.quad = new Quad(this.gl, renderer.state.attribState); - this.renderer._activeRenderTarget.filterData = filterData = { - index:0, - stack:[filterState] - }; + this.shaderCache = {}; + // todo add default! + this.pool = {}; - this.filterData = filterData; - } - - // get the current filter state.. - var currentState = filterData.stack[++filterData.index]; - if(!currentState) - { - currentState = filterData.stack[filterData.index] = new FilterState(); - } - - // for now we go off the filter of the first resolution.. - var resolution = filters[0].resolution; - var padding = filters[0].padding; - var targetBounds = target.filterArea || target.getBounds(true); - var sourceFrame = currentState.sourceFrame; - var destinationFrame = currentState.destinationFrame; - - sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; - sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; - sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; - sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; - - if(filterData.stack[0].renderTarget.transform) - {//jshint ignore:line - - // TODO we should fit the rect around the transform.. - } - else - { - - sourceFrame.fit(filterData.stack[0].destinationFrame); - } - - - destinationFrame.width = sourceFrame.width; - destinationFrame.height = sourceFrame.height; - - - // lets pplay the padding After we fit the element to the screen. - // this should stop the strange side effects that can occour when cropping to the edges - sourceFrame.pad(padding); - - var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); - - currentState.target = target; - currentState.filters = filters; - currentState.resolution = resolution; - currentState.renderTarget = renderTarget; - - // bind the render taget to draw the shape in the top corner.. - - renderTarget.setFrame(destinationFrame, sourceFrame); - // bind the render target - renderer.bindRenderTarget(renderTarget); - - // clear the renderTarget - renderer.clear();//[0.5,0.5,0.5, 1.0]); -}; - -FilterManager.prototype.popFilter = function() -{ - var filterData = this.filterData; - - var lastState = filterData.stack[filterData.index-1]; - var currentState = filterData.stack[filterData.index]; - - this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - - var filters = currentState.filters; - - if(filters.length === 1) - { - filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); - this.freePotRenderTarget(currentState.renderTarget); - } - else - { - var flip = currentState.renderTarget; - var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); - flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - for (var i = 0; i < filters.length-1; i++) - { - filters[i].apply(this, flip, flop, true); - - var t = flip; - flip = flop; - flop = t; - } - - filters[i].apply(this, flip, lastState.renderTarget, false); - - this.freePotRenderTarget(flip); - this.freePotRenderTarget(flop); - } - - filterData.index--; - - if(filterData.index === 0) - { this.filterData = null; } -}; -FilterManager.prototype.applyFilter = function (filter, input, output, clear) -{ - var renderer = this.renderer; - var shader = filter.glShaders[renderer.CONTEXT_UID]; - - // cacheing.. - if(!shader) + pushFilter(target, filters) { - if(filter.glShaderKey) - { - shader = this.shaderCache[filter.glShaderKey]; + var renderer = this.renderer; - if(!shader) - { - shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); - } + var filterData = this.filterData; + + if(!filterData) + { + filterData = this.renderer._activeRenderTarget.filterStack; + + // add new stack + var filterState = new FilterState(); + filterState.sourceFrame = filterState.destinationFrame = this.renderer._activeRenderTarget.size; + filterState.renderTarget = renderer._activeRenderTarget; + + this.renderer._activeRenderTarget.filterData = filterData = { + index:0, + stack:[filterState] + }; + + this.filterData = filterData; + } + + // get the current filter state.. + var currentState = filterData.stack[++filterData.index]; + if(!currentState) + { + currentState = filterData.stack[filterData.index] = new FilterState(); + } + + // for now we go off the filter of the first resolution.. + var resolution = filters[0].resolution; + var padding = filters[0].padding; + var targetBounds = target.filterArea || target.getBounds(true); + var sourceFrame = currentState.sourceFrame; + var destinationFrame = currentState.destinationFrame; + + sourceFrame.x = ((targetBounds.x * resolution) | 0) / resolution; + sourceFrame.y = ((targetBounds.y * resolution) | 0) / resolution; + sourceFrame.width = ((targetBounds.width * resolution) | 0) / resolution; + sourceFrame.height = ((targetBounds.height * resolution) | 0) / resolution; + + if(filterData.stack[0].renderTarget.transform) + {//jshint ignore:line + + // TODO we should fit the rect around the transform.. } else { - shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + + sourceFrame.fit(filterData.stack[0].destinationFrame); } - //TODO - this only needs to be done once? - this.quad.initVao(shader); + + destinationFrame.width = sourceFrame.width; + destinationFrame.height = sourceFrame.height; + + + // lets pplay the padding After we fit the element to the screen. + // this should stop the strange side effects that can occour when cropping to the edges + sourceFrame.pad(padding); + + var renderTarget = this.getPotRenderTarget(renderer.gl, sourceFrame.width, sourceFrame.height, resolution); + + currentState.target = target; + currentState.filters = filters; + currentState.resolution = resolution; + currentState.renderTarget = renderTarget; + + // bind the render taget to draw the shape in the top corner.. + + renderTarget.setFrame(destinationFrame, sourceFrame); + // bind the render target + renderer.bindRenderTarget(renderTarget); + + // clear the renderTarget + renderer.clear();//[0.5,0.5,0.5, 1.0]); } - renderer.bindRenderTarget(output); - - - - if(clear) + popFilter() { - var gl = renderer.gl; + var filterData = this.filterData; - gl.disable(gl.SCISSOR_TEST); - renderer.clear();//[1, 1, 1, 1]); - gl.enable(gl.SCISSOR_TEST); - } + var lastState = filterData.stack[filterData.index-1]; + var currentState = filterData.stack[filterData.index]; - // in case the render target is being masked using a scissor rect - if(output === renderer.maskManager.scissorRenderTarget) - { - renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); - } + this.quad.map(currentState.renderTarget.size, currentState.sourceFrame).upload(); - renderer.bindShader(shader); + var filters = currentState.filters; - // this syncs the pixi filters uniforms with glsl uniforms - this.syncUniforms(shader, filter); - - // bind the input texture.. - input.texture.bind(0); - // when you manually bind a texture, please switch active texture location to it - renderer._activeTextureLocation = 0; - - renderer.state.setBlendMode( filter.blendMode ); - - this.quad.draw(); -}; - -// this returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.syncUniforms = function (shader, filter) -{ - var uniformData = filter.uniformData; - var uniforms = filter.uniforms; - - // 0 is reserverd for the pixi texture so we start at 1! - var textureCount = 1; - var currentState; - - if(shader.uniforms.data.filterArea) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterArea = shader.uniforms.filterArea; - - filterArea[0] = currentState.renderTarget.size.width; - filterArea[1] = currentState.renderTarget.size.height; - filterArea[2] = currentState.sourceFrame.x; - filterArea[3] = currentState.sourceFrame.y; - - shader.uniforms.filterArea = filterArea; - } - - // use this to clamp displaced texture coords so they belong to filterArea - // see displacementFilter fragment shader for an example - if(shader.uniforms.data.filterClamp) - { - currentState = this.filterData.stack[this.filterData.index]; - var filterClamp = shader.uniforms.filterClamp; - - filterClamp[0] = 0.5 / currentState.renderTarget.size.width; - filterClamp[1] = 0.5 / currentState.renderTarget.size.height; - filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; - filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; - - shader.uniforms.filterClamp = filterClamp; - } - - var val; - //TODO Cacheing layer.. - for(var i in uniformData) - { - if(uniformData[i].type === 'sampler2D') + if(filters.length === 1) { - shader.uniforms[i] = textureCount; + filters[0].apply(this, currentState.renderTarget, lastState.renderTarget, false); + this.freePotRenderTarget(currentState.renderTarget); + } + else + { + var flip = currentState.renderTarget; + var flop = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, 1); + flop.setFrame(currentState.destinationFrame, currentState.sourceFrame); - if(uniforms[i].baseTexture) + for (var i = 0; i < filters.length-1; i++) { - this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + filters[i].apply(this, flip, flop, true); + + var t = flip; + flip = flop; + flop = t; + } + + filters[i].apply(this, flip, lastState.renderTarget, false); + + this.freePotRenderTarget(flip); + this.freePotRenderTarget(flop); + } + + filterData.index--; + + if(filterData.index === 0) + { + this.filterData = null; + } + } + + applyFilter(filter, input, output, clear) + { + var renderer = this.renderer; + var shader = filter.glShaders[renderer.CONTEXT_UID]; + + // cacheing.. + if(!shader) + { + if(filter.glShaderKey) + { + shader = this.shaderCache[filter.glShaderKey]; + + if(!shader) + { + shader = filter.glShaders[renderer.CONTEXT_UID] = this.shaderCache[filter.glShaderKey] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); + } } else { - // this is helpful as renderTargets can also be set. - // Although thinking about it, we could probably - // make the filter texture cache return a RenderTexture - // rather than a renderTarget - var gl = this.renderer.gl; - this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; - gl.activeTexture(gl.TEXTURE0 + textureCount ); - uniforms[i].texture.bind(); + shader = filter.glShaders[renderer.CONTEXT_UID] = new Shader(this.gl, filter.vertexSrc, filter.fragmentSrc); } - textureCount++; + //TODO - this only needs to be done once? + this.quad.initVao(shader); } - else if(uniformData[i].type === 'mat3') + + renderer.bindRenderTarget(output); + + + + if(clear) { - // check if its pixi matrix.. - if(uniforms[i].a !== undefined) + var gl = renderer.gl; + + gl.disable(gl.SCISSOR_TEST); + renderer.clear();//[1, 1, 1, 1]); + gl.enable(gl.SCISSOR_TEST); + } + + // in case the render target is being masked using a scissor rect + if(output === renderer.maskManager.scissorRenderTarget) + { + renderer.maskManager.pushScissorMask(null, renderer.maskManager.scissorData); + } + + renderer.bindShader(shader); + + // this syncs the pixi filters uniforms with glsl uniforms + this.syncUniforms(shader, filter); + + // bind the input texture.. + input.texture.bind(0); + // when you manually bind a texture, please switch active texture location to it + renderer._activeTextureLocation = 0; + + renderer.state.setBlendMode( filter.blendMode ); + + this.quad.draw(); + } + + // this returns a matrix that will normalise map filter cords in the filter to screen space + syncUniforms(shader, filter) + { + var uniformData = filter.uniformData; + var uniforms = filter.uniforms; + + // 0 is reserverd for the pixi texture so we start at 1! + var textureCount = 1; + var currentState; + + if(shader.uniforms.data.filterArea) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterArea = shader.uniforms.filterArea; + + filterArea[0] = currentState.renderTarget.size.width; + filterArea[1] = currentState.renderTarget.size.height; + filterArea[2] = currentState.sourceFrame.x; + filterArea[3] = currentState.sourceFrame.y; + + shader.uniforms.filterArea = filterArea; + } + + // use this to clamp displaced texture coords so they belong to filterArea + // see displacementFilter fragment shader for an example + if(shader.uniforms.data.filterClamp) + { + currentState = this.filterData.stack[this.filterData.index]; + var filterClamp = shader.uniforms.filterClamp; + + filterClamp[0] = 0.5 / currentState.renderTarget.size.width; + filterClamp[1] = 0.5 / currentState.renderTarget.size.height; + filterClamp[2] = (currentState.sourceFrame.width - 0.5) / currentState.renderTarget.size.width; + filterClamp[3] = (currentState.sourceFrame.height - 0.5) / currentState.renderTarget.size.height; + + shader.uniforms.filterClamp = filterClamp; + } + + var val; + //TODO Cacheing layer.. + for(var i in uniformData) + { + if(uniformData[i].type === 'sampler2D') { - shader.uniforms[i] = uniforms[i].toArray(true); + shader.uniforms[i] = textureCount; + + if(uniforms[i].baseTexture) + { + this.renderer.bindTexture(uniforms[i].baseTexture, textureCount); + } + else + { + // this is helpful as renderTargets can also be set. + // Although thinking about it, we could probably + // make the filter texture cache return a RenderTexture + // rather than a renderTarget + var gl = this.renderer.gl; + this.renderer._activeTextureLocation = gl.TEXTURE0 + textureCount; + gl.activeTexture(gl.TEXTURE0 + textureCount ); + uniforms[i].texture.bind(); + } + + textureCount++; + } + else if(uniformData[i].type === 'mat3') + { + // check if its pixi matrix.. + if(uniforms[i].a !== undefined) + { + shader.uniforms[i] = uniforms[i].toArray(true); + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'vec2') + { + //check if its a point.. + if(uniforms[i].x !== undefined) + { + val = shader.uniforms[i] || new Float32Array(2); + val[0] = uniforms[i].x; + val[1] = uniforms[i].y; + shader.uniforms[i] = val; + } + else + { + shader.uniforms[i] = uniforms[i]; + } + } + else if(uniformData[i].type === 'float') + { + if(shader.uniforms.data[i].value !== uniformData[i]) + { + shader.uniforms[i] = uniforms[i]; + } } else { shader.uniforms[i] = uniforms[i]; } } - else if(uniformData[i].type === 'vec2') - { - //check if its a point.. - if(uniforms[i].x !== undefined) - { - val = shader.uniforms[i] || new Float32Array(2); - val[0] = uniforms[i].x; - val[1] = uniforms[i].y; - shader.uniforms[i] = val; - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } - else if(uniformData[i].type === 'float') - { - if(shader.uniforms.data[i].value !== uniformData[i]) - { - shader.uniforms[i] = uniforms[i]; - } - } - else - { - shader.uniforms[i] = uniforms[i]; - } - } -}; - - -FilterManager.prototype.getRenderTarget = function(clear, resolution) -{ - var currentState = this.filterData.stack[this.filterData.index]; - var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); - renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); - - return renderTarget; -}; - -FilterManager.prototype.returnRenderTarget = function(renderTarget) -{ - return this.freePotRenderTarget(renderTarget); -}; - -/* - * Calculates the mapped matrix - * @param filterArea {Rectangle} The filter area - * @param sprite {Sprite} the target sprite - * @param outputMatrix {Matrix} @alvin - */ -// TODO playing around here.. this is temporary - (will end up in the shader) -// thia returns a matrix that will normalise map filter cords in the filter to screen space -FilterManager.prototype.calculateScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); -}; - -/** - * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea - * - * @param outputMatrix {PIXI.Matrix} - */ -FilterManager.prototype.calculateNormalizedScreenSpaceMatrix = function (outputMatrix) -{ - var currentState = this.filterData.stack[this.filterData.index]; - - return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); -}; - -// this will map the filter coord so that a texture can be used based on the transform of a sprite -FilterManager.prototype.calculateSpriteMatrix = function (outputMatrix, sprite) -{ - var currentState = this.filterData.stack[this.filterData.index]; - return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); -}; - -FilterManager.prototype.destroy = function() -{ - this.shaderCache = []; - this.emptyPool(); -}; - - - -//TODO move to a seperate class could be on renderer? -//also - could cause issue with multiple contexts? -FilterManager.prototype.getPotRenderTarget = function(gl, minWidth, minHeight, resolution) -{ - //TODO you coud return a bigger texture if there is not one in the pool? - minWidth = bitTwiddle.nextPow2(minWidth * resolution); - minHeight = bitTwiddle.nextPow2(minHeight * resolution); - - var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); - - if(!this.pool[key]) { - this.pool[key] = []; } - var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); - //manually tweak the resolution... - //this will not modify the size of the frame buffer, just its resolution. - renderTarget.resolution = resolution; - renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; - renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; - return renderTarget; -}; - -FilterManager.prototype.emptyPool = function() -{ - for (var i in this.pool) + getRenderTarget(clear, resolution) { - var textures = this.pool[i]; - if(textures) - { - for (var j = 0; j < textures.length; j++) - { - textures[j].destroy(true); - } - } + var currentState = this.filterData.stack[this.filterData.index]; + var renderTarget = this.getPotRenderTarget(this.renderer.gl, currentState.sourceFrame.width, currentState.sourceFrame.height, resolution || currentState.resolution); + renderTarget.setFrame(currentState.destinationFrame, currentState.sourceFrame); + + return renderTarget; } - this.pool = {}; -}; + returnRenderTarget(renderTarget) + { + return this.freePotRenderTarget(renderTarget); + } -FilterManager.prototype.freePotRenderTarget = function(renderTarget) -{ - var minWidth = renderTarget.size.width * renderTarget.resolution; - var minHeight = renderTarget.size.height * renderTarget.resolution; + /* + * Calculates the mapped matrix + * @param filterArea {Rectangle} The filter area + * @param sprite {Sprite} the target sprite + * @param outputMatrix {Matrix} @alvin + */ + // TODO playing around here.. this is temporary - (will end up in the shader) + // thia returns a matrix that will normalise map filter cords in the filter to screen space + calculateScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size); + } - var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); - this.pool[key].push(renderTarget); -}; + /** + * Multiply vTextureCoord to this matrix to achieve (0,0,1,1) for filterArea + * + * @param outputMatrix {PIXI.Matrix} + */ + calculateNormalizedScreenSpaceMatrix(outputMatrix) + { + var currentState = this.filterData.stack[this.filterData.index]; + + return filterTransforms.calculateNormalizedScreenSpaceMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, currentState.destinationFrame); + } + + // this will map the filter coord so that a texture can be used based on the transform of a sprite + calculateSpriteMatrix(outputMatrix, sprite) + { + var currentState = this.filterData.stack[this.filterData.index]; + return filterTransforms.calculateSpriteMatrix(outputMatrix, currentState.sourceFrame, currentState.renderTarget.size, sprite); + } + + destroy() + { + this.shaderCache = []; + this.emptyPool(); + } + + + + //TODO move to a seperate class could be on renderer? + //also - could cause issue with multiple contexts? + getPotRenderTarget(gl, minWidth, minHeight, resolution) + { + //TODO you coud return a bigger texture if there is not one in the pool? + minWidth = bitTwiddle.nextPow2(minWidth * resolution); + minHeight = bitTwiddle.nextPow2(minHeight * resolution); + + var key = ((minWidth & 0xFFFF) << 16) | ( minHeight & 0xFFFF); + + if(!this.pool[key]) { + this.pool[key] = []; + } + + var renderTarget = this.pool[key].pop() || new RenderTarget(gl, minWidth, minHeight, null, 1); + + //manually tweak the resolution... + //this will not modify the size of the frame buffer, just its resolution. + renderTarget.resolution = resolution; + renderTarget.defaultFrame.width = renderTarget.size.width = minWidth / resolution; + renderTarget.defaultFrame.height = renderTarget.size.height = minHeight / resolution; + return renderTarget; + } + + emptyPool() + { + for (var i in this.pool) + { + var textures = this.pool[i]; + if(textures) + { + for (var j = 0; j < textures.length; j++) + { + textures[j].destroy(true); + } + } + } + + this.pool = {}; + } + + freePotRenderTarget(renderTarget) + { + var minWidth = renderTarget.size.width * renderTarget.resolution; + var minHeight = renderTarget.size.height * renderTarget.resolution; + + var key = ((minWidth & 0xFFFF) << 16) | (minHeight & 0xFFFF); + this.pool[key].push(renderTarget); + } + +} + +module.exports = FilterManager; diff --git a/src/core/renderers/webgl/managers/MaskManager.js b/src/core/renderers/webgl/managers/MaskManager.js index 6d00bc1..ae5f9db 100644 --- a/src/core/renderers/webgl/managers/MaskManager.js +++ b/src/core/renderers/webgl/managers/MaskManager.js @@ -6,188 +6,189 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function MaskManager(renderer) -{ - WebGLManager.call(this, renderer); - - //TODO - we don't need both! - this.scissor = false; - this.scissorData = null; - this.scissorRenderTarget = null; - - this.enableScissor = true; - - this.alphaMaskPool = []; - this.alphaMaskIndex = 0; -} - -MaskManager.prototype = Object.create(WebGLManager.prototype); -MaskManager.prototype.constructor = MaskManager; -module.exports = MaskManager; - -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.DisplayObject} Display Object to push the mask to - * @param maskData {PIXI.Sprite|PIXI.Graphics} - */ -MaskManager.prototype.pushMask = function (target, maskData) -{ - if (maskData.texture) +class MaskManager extends WebGLManager { + constructor(renderer) { - this.pushSpriteMask(target, maskData); + super(renderer); + + //TODO - we don't need both! + this.scissor = false; + this.scissorData = null; + this.scissorRenderTarget = null; + + this.enableScissor = true; + + this.alphaMaskPool = []; + this.alphaMaskIndex = 0; } - else + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.DisplayObject} Display Object to push the mask to + * @param maskData {PIXI.Sprite|PIXI.Graphics} + */ + pushMask(target, maskData) { - if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) + if (maskData.texture) { - var matrix = maskData.worldTransform; - - var rot = Math.atan2(matrix.b, matrix.a); - - // use the nearest degree! - rot = Math.round(rot * (180/Math.PI)); - - if(rot % 90) + this.pushSpriteMask(target, maskData); + } + else + { + if(this.enableScissor && !this.scissor && !this.renderer.stencilManager.stencilMaskStack.length && maskData.isFastRect()) { - this.pushStencilMask(maskData); + var matrix = maskData.worldTransform; + + var rot = Math.atan2(matrix.b, matrix.a); + + // use the nearest degree! + rot = Math.round(rot * (180/Math.PI)); + + if(rot % 90) + { + this.pushStencilMask(maskData); + } + else + { + this.pushScissorMask(target, maskData); + } } else { - this.pushScissorMask(target, maskData); + this.pushStencilMask(maskData); } } - else - { - this.pushStencilMask(maskData); - } } -}; -/** - * Removes the last mask from the mask stack and doesn't return it. - * - * @param target {PIXI.DisplayObject} Display Object to pop the mask from - * @param maskData {Array<*>} - */ -MaskManager.prototype.popMask = function (target, maskData) -{ - if (maskData.texture) + /** + * Removes the last mask from the mask stack and doesn't return it. + * + * @param target {PIXI.DisplayObject} Display Object to pop the mask from + * @param maskData {Array<*>} + */ + popMask(target, maskData) { - this.popSpriteMask(target, maskData); - } - else - { - if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + if (maskData.texture) { - this.popScissorMask(target, maskData); + this.popSpriteMask(target, maskData); } else { - this.popStencilMask(target, maskData); + if(this.enableScissor && !this.renderer.stencilManager.stencilMaskStack.length) + { + this.popScissorMask(target, maskData); + } + else + { + this.popStencilMask(target, maskData); + } + + } + } + + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to + * @param maskData {PIXI.Sprite} Sprite to be used as the mask + */ + pushSpriteMask(target, maskData) + { + var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; + + if (!alphaMaskFilter) + { + alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; } + alphaMaskFilter[0].resolution = this.renderer.resolution; + alphaMaskFilter[0].maskSprite = maskData; + + //TODO - may cause issues! + target.filterArea = maskData.getBounds(true); + + this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + + this.alphaMaskIndex++; } -}; -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param target {PIXI.RenderTarget} Display Object to push the sprite mask to - * @param maskData {PIXI.Sprite} Sprite to be used as the mask - */ -MaskManager.prototype.pushSpriteMask = function (target, maskData) -{ - var alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex]; - - if (!alphaMaskFilter) + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popSpriteMask() { - alphaMaskFilter = this.alphaMaskPool[this.alphaMaskIndex] = [new AlphaMaskFilter(maskData)]; + this.renderer.filterManager.popFilter(); + this.alphaMaskIndex--; } - alphaMaskFilter[0].resolution = this.renderer.resolution; - alphaMaskFilter[0].maskSprite = maskData; - //TODO - may cause issues! - target.filterArea = maskData.getBounds(true); + /** + * Applies the Mask and adds it to the current filter stack. + * + * @param maskData {Array<*>} + */ + pushStencilMask(maskData) + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.pushStencil(maskData); + } - this.renderer.filterManager.pushFilter(target, alphaMaskFilter); + /** + * Removes the last filter from the filter stack and doesn't return it. + * + */ + popStencilMask() + { + this.renderer.currentRenderer.stop(); + this.renderer.stencilManager.popStencil(); + } - this.alphaMaskIndex++; -}; + /** + * + * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to + * @param maskData + */ + pushScissorMask(target, maskData) + { + maskData.renderable = true; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popSpriteMask = function () -{ - this.renderer.filterManager.popFilter(); - this.alphaMaskIndex--; -}; + var renderTarget = this.renderer._activeRenderTarget; + var bounds = maskData.getBounds(); -/** - * Applies the Mask and adds it to the current filter stack. - * - * @param maskData {Array<*>} - */ -MaskManager.prototype.pushStencilMask = function (maskData) -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.pushStencil(maskData); -}; + bounds.fit(renderTarget.size); + maskData.renderable = false; -/** - * Removes the last filter from the filter stack and doesn't return it. - * - */ -MaskManager.prototype.popStencilMask = function () -{ - this.renderer.currentRenderer.stop(); - this.renderer.stencilManager.popStencil(); -}; + this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); -/** - * - * @param target {PIXI.RenderTarget} Display Object to push the scissor mask to - * @param maskData - */ -MaskManager.prototype.pushScissorMask = function (target, maskData) -{ - maskData.renderable = true; + var resolution = this.renderer.resolution; + this.renderer.gl.scissor(bounds.x * resolution, + (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, + bounds.width * resolution, + bounds.height * resolution); - var renderTarget = this.renderer._activeRenderTarget; + this.scissorRenderTarget = renderTarget; + this.scissorData = maskData; + this.scissor = true; + } - var bounds = maskData.getBounds(); + /** + * + * + */ + popScissorMask() + { + this.scissorRenderTarget = null; + this.scissorData = null; + this.scissor = false; - bounds.fit(renderTarget.size); - maskData.renderable = false; + // must be scissor! + var gl = this.renderer.gl; + gl.disable(gl.SCISSOR_TEST); + } - this.renderer.gl.enable(this.renderer.gl.SCISSOR_TEST); +} - var resolution = this.renderer.resolution; - this.renderer.gl.scissor(bounds.x * resolution, - (renderTarget.root ? renderTarget.size.height - bounds.y - bounds.height : bounds.y) * resolution, - bounds.width * resolution, - bounds.height * resolution); - - this.scissorRenderTarget = renderTarget; - this.scissorData = maskData; - this.scissor = true; -}; - -/** - * - * - */ -MaskManager.prototype.popScissorMask = function () -{ - this.scissorRenderTarget = null; - this.scissorData = null; - this.scissor = false; - - // must be scissor! - var gl = this.renderer.gl; - gl.disable(gl.SCISSOR_TEST); -}; +module.exports = MaskManager; diff --git a/src/core/renderers/webgl/managers/StencilManager.js b/src/core/renderers/webgl/managers/StencilManager.js index 6e3d2d2..f81ca6d 100644 --- a/src/core/renderers/webgl/managers/StencilManager.js +++ b/src/core/renderers/webgl/managers/StencilManager.js @@ -5,107 +5,108 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function StencilManager(renderer) -{ - WebGLManager.call(this, renderer); - this.stencilMaskStack = null; -} - -StencilManager.prototype = Object.create(WebGLManager.prototype); -StencilManager.prototype.constructor = StencilManager; -module.exports = StencilManager; - -/** - * Changes the mask stack that is used by this manager. - * - * @param stencilMaskStack {PIXI.Graphics[]} The mask stack - */ -StencilManager.prototype.setMaskStack = function ( stencilMaskStack ) -{ - this.stencilMaskStack = stencilMaskStack; - - var gl = this.renderer.gl; - - if (stencilMaskStack.length === 0) +class StencilManager extends WebGLManager { + constructor(renderer) { - gl.disable(gl.STENCIL_TEST); - } - else - { - gl.enable(gl.STENCIL_TEST); - } -}; - -/** - * Applies the Mask and adds it to the current filter stack. @alvin - * - * @param graphics {PIXI.Graphics} - */ -StencilManager.prototype.pushStencil = function (graphics) -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - this.renderer._activeRenderTarget.attachStencilBuffer(); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - if (sms.length === 0) - { - gl.enable(gl.STENCIL_TEST); - gl.clear(gl.STENCIL_BUFFER_BIT); - gl.stencilFunc(gl.ALWAYS,1,1); + super(renderer); + this.stencilMaskStack = null; } - sms.push(graphics); - - gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); - - this.renderer.plugins.graphics.render(graphics); - - gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL,0, sms.length); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); -}; - -/** - * TODO @alvin - */ -StencilManager.prototype.popStencil = function () -{ - this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - - var gl = this.renderer.gl, - sms = this.stencilMaskStack; - - var graphics = sms.pop(); - - if (sms.length === 0) + /** + * Changes the mask stack that is used by this manager. + * + * @param stencilMaskStack {PIXI.Graphics[]} The mask stack + */ + setMaskStack( stencilMaskStack ) { - // the stack is empty! - gl.disable(gl.STENCIL_TEST); + this.stencilMaskStack = stencilMaskStack; + + var gl = this.renderer.gl; + + if (stencilMaskStack.length === 0) + { + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.enable(gl.STENCIL_TEST); + } } - else + + /** + * Applies the Mask and adds it to the current filter stack. @alvin + * + * @param graphics {PIXI.Graphics} + */ + pushStencil(graphics) { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); + + this.renderer._activeRenderTarget.attachStencilBuffer(); + + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + if (sms.length === 0) + { + gl.enable(gl.STENCIL_TEST); + gl.clear(gl.STENCIL_BUFFER_BIT); + gl.stencilFunc(gl.ALWAYS,1,1); + } + + sms.push(graphics); + gl.colorMask(false, false, false, false); - gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.INCR); this.renderer.plugins.graphics.render(graphics); gl.colorMask(true, true, true, true); - gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilFunc(gl.NOTEQUAL,0, sms.length); gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); } -}; -/** - * Destroys the mask stack. - * - */ -StencilManager.prototype.destroy = function () -{ - WebGLManager.prototype.destroy.call(this); + /** + * TODO @alvin + */ + popStencil() + { + this.renderer.setObjectRenderer(this.renderer.plugins.graphics); - this.stencilMaskStack.stencilStack = null; -}; + var gl = this.renderer.gl, + sms = this.stencilMaskStack; + + var graphics = sms.pop(); + + if (sms.length === 0) + { + // the stack is empty! + gl.disable(gl.STENCIL_TEST); + } + else + { + gl.colorMask(false, false, false, false); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.DECR); + + this.renderer.plugins.graphics.render(graphics); + + gl.colorMask(true, true, true, true); + gl.stencilFunc(gl.NOTEQUAL, 0, sms.length); + gl.stencilOp(gl.KEEP,gl.KEEP,gl.KEEP); + } + } + + /** + * Destroys the mask stack. + * + */ + destroy() + { + WebGLManager.prototype.destroy.call(this); + + this.stencilMaskStack.stencilStack = null; + } + +} + +module.exports = StencilManager; diff --git a/src/core/renderers/webgl/managers/WebGLManager.js b/src/core/renderers/webgl/managers/WebGLManager.js index 0d75102..f9a0f52 100644 --- a/src/core/renderers/webgl/managers/WebGLManager.js +++ b/src/core/renderers/webgl/managers/WebGLManager.js @@ -3,37 +3,39 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this manager works for. */ -function WebGLManager(renderer) -{ - /** - * The renderer this manager works for. - * - * @member {PIXI.WebGLRenderer} - */ - this.renderer = renderer; +class WebGLManager { + constructor(renderer) + { + /** + * The renderer this manager works for. + * + * @member {PIXI.WebGLRenderer} + */ + this.renderer = renderer; - this.renderer.on('context', this.onContextChange, this); + this.renderer.on('context', this.onContextChange, this); + } + + /** + * Generic method called when there is a WebGL context change. + * + */ + onContextChange() + { + // do some codes init! + } + + /** + * Generic destroy methods to be overridden by the subclass + * + */ + destroy() + { + this.renderer.off('context', this.onContextChange, this); + + this.renderer = null; + } + } -WebGLManager.prototype.constructor = WebGLManager; module.exports = WebGLManager; - -/** - * Generic method called when there is a WebGL context change. - * - */ -WebGLManager.prototype.onContextChange = function () -{ - // do some codes init! -}; - -/** - * Generic destroy methods to be overridden by the subclass - * - */ -WebGLManager.prototype.destroy = function () -{ - this.renderer.off('context', this.onContextChange, this); - - this.renderer = null; -}; diff --git a/src/core/renderers/webgl/utils/ObjectRenderer.js b/src/core/renderers/webgl/utils/ObjectRenderer.js index 186ba88..4cc4323 100644 --- a/src/core/renderers/webgl/utils/ObjectRenderer.js +++ b/src/core/renderers/webgl/utils/ObjectRenderer.js @@ -8,49 +8,49 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this object renderer works for. */ -function ObjectRenderer(renderer) -{ - WebGLManager.call(this, renderer); +class ObjectRenderer extends WebGLManager { + constructor(renderer) + { + super(renderer); + } + + /** + * Starts the renderer and sets the shader + * + */ + start() + { + // set the shader.. + } + + /** + * Stops the renderer + * + */ + stop() + { + this.flush(); + } + + /** + * Stub method for rendering content and emptying the current batch. + * + */ + flush() + { + // flush! + } + + /** + * Renders an object + * + * @param object {PIXI.DisplayObject} The object to render. + */ + render(object) // jshint unused:false + { + // render the object + } + } - -ObjectRenderer.prototype = Object.create(WebGLManager.prototype); -ObjectRenderer.prototype.constructor = ObjectRenderer; module.exports = ObjectRenderer; - -/** - * Starts the renderer and sets the shader - * - */ -ObjectRenderer.prototype.start = function () -{ - // set the shader.. -}; - -/** - * Stops the renderer - * - */ -ObjectRenderer.prototype.stop = function () -{ - this.flush(); -}; - -/** - * Stub method for rendering content and emptying the current batch. - * - */ -ObjectRenderer.prototype.flush = function () -{ - // flush! -}; - -/** - * Renders an object - * - * @param object {PIXI.DisplayObject} The object to render. - */ -ObjectRenderer.prototype.render = function (object) // jshint unused:false -{ - // render the object -}; diff --git a/src/core/renderers/webgl/utils/Quad.js b/src/core/renderers/webgl/utils/Quad.js index 678261f..8ddc4cc 100644 --- a/src/core/renderers/webgl/utils/Quad.js +++ b/src/core/renderers/webgl/utils/Quad.js @@ -9,163 +9,164 @@ * @param gl {WebGLRenderingContext} The gl context for this quad to use. * @param state {object} TODO: Description */ -function Quad(gl, state) -{ - /* - * the current WebGL drawing context - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; +class Quad { + constructor(gl, state) + { + /* + * the current WebGL drawing context + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - /** - * An array of vertices - * - * @member {Float32Array} - */ - this.vertices = new Float32Array([ - -1,-1, - 1,-1, - 1,1, - -1,1 - ]); + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = new Float32Array([ + -1,-1, + 1,-1, + 1,1, + -1,1 + ]); - /** - * The Uvs of the quad - * - * @member {Float32Array} - */ - this.uvs = new Float32Array([ - 0,0, - 1,0, - 1,1, - 0,1 - ]); + /** + * The Uvs of the quad + * + * @member {Float32Array} + */ + this.uvs = new Float32Array([ + 0,0, + 1,0, + 1,1, + 0,1 + ]); - this.interleaved = new Float32Array(8 * 2); + this.interleaved = new Float32Array(8 * 2); - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + this.indices = createIndicesForQuads(1); + + /* + * @member {glCore.GLBuffer} The vertex buffer + */ + this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + + /* + * @member {glCore.GLBuffer} The index buffer + */ + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + /* + * @member {glCore.VertexArrayObject} The index buffer + */ + this.vao = new glCore.VertexArrayObject(gl, state); + } - /* - * @member {Uint16Array} An array containing the indices of the vertices + /** + * Initialises the vaos and uses the shader + * @param shader {PIXI.Shader} the shader to use */ - this.indices = createIndicesForQuads(1); + initVao(shader) + { + this.vao.clear() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) + .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); + } - /* - * @member {glCore.GLBuffer} The vertex buffer + /** + * Maps two Rectangle to the quad + * @param targetTextureFrame {PIXI.Rectangle} the first rectangle + * @param destinationFrame {PIXI.Rectangle} the second rectangle */ - this.vertexBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.interleaved, gl.STATIC_DRAW); + map(targetTextureFrame, destinationFrame) + { + var x = 0; //destinationFrame.x / targetTextureFrame.width; + var y = 0; //destinationFrame.y / targetTextureFrame.height; - /* - * @member {glCore.GLBuffer} The index buffer - */ - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + this.uvs[0] = x; + this.uvs[1] = y; - /* - * @member {glCore.VertexArrayObject} The index buffer + this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[3] = y; + + this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; + this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; + + this.uvs[6] = x; + this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; + + /// ----- + x = destinationFrame.x; + y = destinationFrame.y; + + this.vertices[0] = x; + this.vertices[1] = y; + + this.vertices[2] = x + destinationFrame.width; + this.vertices[3] = y; + + this.vertices[4] = x + destinationFrame.width; + this.vertices[5] = y + destinationFrame.height; + + this.vertices[6] = x; + this.vertices[7] = y + destinationFrame.height; + + return this; + } + + /** + * Draws the quad */ - this.vao = new glCore.VertexArrayObject(gl, state); + draw() + { + this.vao.bind() + .draw(this.gl.TRIANGLES, 6, 0) + .unbind(); + + return this; + } + + /** + * Binds the buffer and uploads the data + */ + upload() + { + for (var i = 0; i < 4; i++) { + this.interleaved[i*4] = this.vertices[(i*2)]; + this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; + this.interleaved[(i*4)+2] = this.uvs[i*2]; + this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; + } + + this.vertexBuffer.upload(this.interleaved); + + return this; + } + + /** + * Removes this quad from WebGL + */ + destroy() + { + var gl = this.gl; + + gl.deleteBuffer(this.vertexBuffer); + gl.deleteBuffer(this.indexBuffer); + } } -Quad.prototype.constructor = Quad; - -/** - * Initialises the vaos and uses the shader - * @param shader {PIXI.Shader} the shader to use - */ -Quad.prototype.initVao = function(shader) -{ - this.vao.clear() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffer, shader.attributes.aVertexPosition, this.gl.FLOAT, false, 4 * 4, 0) - .addAttribute(this.vertexBuffer, shader.attributes.aTextureCoord, this.gl.FLOAT, false, 4 * 4, 2 * 4); -}; - -/** - * Maps two Rectangle to the quad - * @param targetTextureFrame {PIXI.Rectangle} the first rectangle - * @param destinationFrame {PIXI.Rectangle} the second rectangle - */ -Quad.prototype.map = function(targetTextureFrame, destinationFrame) -{ - var x = 0; //destinationFrame.x / targetTextureFrame.width; - var y = 0; //destinationFrame.y / targetTextureFrame.height; - - this.uvs[0] = x; - this.uvs[1] = y; - - this.uvs[2] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[3] = y; - - this.uvs[4] = x + destinationFrame.width / targetTextureFrame.width; - this.uvs[5] = y + destinationFrame.height / targetTextureFrame.height; - - this.uvs[6] = x; - this.uvs[7] = y + destinationFrame.height / targetTextureFrame.height; - - /// ----- - x = destinationFrame.x; - y = destinationFrame.y; - - this.vertices[0] = x; - this.vertices[1] = y; - - this.vertices[2] = x + destinationFrame.width; - this.vertices[3] = y; - - this.vertices[4] = x + destinationFrame.width; - this.vertices[5] = y + destinationFrame.height; - - this.vertices[6] = x; - this.vertices[7] = y + destinationFrame.height; - - return this; -}; - -/** - * Draws the quad - */ -Quad.prototype.draw = function() -{ - this.vao.bind() - .draw(this.gl.TRIANGLES, 6, 0) - .unbind(); - - return this; -}; - -/** - * Binds the buffer and uploads the data - */ -Quad.prototype.upload = function() -{ - for (var i = 0; i < 4; i++) { - this.interleaved[i*4] = this.vertices[(i*2)]; - this.interleaved[(i*4)+1] = this.vertices[(i*2)+1]; - this.interleaved[(i*4)+2] = this.uvs[i*2]; - this.interleaved[(i*4)+3] = this.uvs[(i*2)+1]; - } - - this.vertexBuffer.upload(this.interleaved); - - return this; -}; - -/** - * Removes this quad from WebGL - */ -Quad.prototype.destroy = function() -{ - var gl = this.gl; - - gl.deleteBuffer(this.vertexBuffer); - gl.deleteBuffer(this.indexBuffer); -}; - module.exports = Quad; diff --git a/src/core/renderers/webgl/utils/RenderTarget.js b/src/core/renderers/webgl/utils/RenderTarget.js index aec00f3..481f332 100644 --- a/src/core/renderers/webgl/utils/RenderTarget.js +++ b/src/core/renderers/webgl/utils/RenderTarget.js @@ -16,303 +16,305 @@ * @param [resolution=1] {number} The current resolution / device pixel ratio * @param [root=false] {boolean} Whether this object is the root element or not */ -var RenderTarget = function(gl, width, height, scaleMode, resolution, root) -{ - //TODO Resolution could go here ( eg low res blurs ) - - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - // next time to create a frame buffer and texture - - /** - * A frame buffer - * - * @member {PIXI.glCore.GLFramebuffer} - */ - this.frameBuffer = null; - - /** - * The texture - * - * @member {PIXI.glCore.GLTexture} - */ - this.texture = null; - - /** - * The background colour of this render target, as an array of [r,g,b,a] values - * - * @member {number[]} - */ - this.clearColor = [0, 0, 0, 0]; - - /** - * The size of the object as a rectangle - * - * @member {PIXI.Rectangle} - */ - this.size = new math.Rectangle(0, 0, 1, 1); - - /** - * The current resolution / device pixel ratio - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The projection matrix - * - * @member {PIXI.Matrix} - */ - this.projectionMatrix = new math.Matrix(); - - /** - * The object's transform - * - * @member {PIXI.Matrix} - */ - this.transform = null; - - /** - * The frame. - * - * @member {PIXI.Rectangle} - */ - this.frame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.defaultFrame = new math.Rectangle(); - this.destinationFrame = null; - this.sourceFrame = null; - - /** - * The stencil buffer stores masking data for the render target - * - * @member {glCore.GLBuffer} - */ - this.stencilBuffer = null; - - /** - * The data structure for the stencil masks - * - * @member {PIXI.Graphics[]} - */ - this.stencilMaskStack = []; - - /** - * Stores filter data for the render target - * - * @member {object[]} - */ - this.filterData = null; - - /** - * The scale mode. - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Whether this object is the root element or not - * - * @member {boolean} - */ - this.root = root; - - - if (!this.root) +class RenderTarget { + constructor(gl, width, height, scaleMode, resolution, root) { - this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + //TODO Resolution could go here ( eg low res blurs ) - if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; + + // next time to create a frame buffer and texture + + /** + * A frame buffer + * + * @member {PIXI.glCore.GLFramebuffer} + */ + this.frameBuffer = null; + + /** + * The texture + * + * @member {PIXI.glCore.GLTexture} + */ + this.texture = null; + + /** + * The background colour of this render target, as an array of [r,g,b,a] values + * + * @member {number[]} + */ + this.clearColor = [0, 0, 0, 0]; + + /** + * The size of the object as a rectangle + * + * @member {PIXI.Rectangle} + */ + this.size = new math.Rectangle(0, 0, 1, 1); + + /** + * The current resolution / device pixel ratio + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; + + /** + * The projection matrix + * + * @member {PIXI.Matrix} + */ + this.projectionMatrix = new math.Matrix(); + + /** + * The object's transform + * + * @member {PIXI.Matrix} + */ + this.transform = null; + + /** + * The frame. + * + * @member {PIXI.Rectangle} + */ + this.frame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.defaultFrame = new math.Rectangle(); + this.destinationFrame = null; + this.sourceFrame = null; + + /** + * The stencil buffer stores masking data for the render target + * + * @member {glCore.GLBuffer} + */ + this.stencilBuffer = null; + + /** + * The data structure for the stencil masks + * + * @member {PIXI.Graphics[]} + */ + this.stencilMaskStack = []; + + /** + * Stores filter data for the render target + * + * @member {object[]} + */ + this.filterData = null; + + /** + * The scale mode. + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + + /** + * Whether this object is the root element or not + * + * @member {boolean} + */ + this.root = root; + + + if (!this.root) { - this.frameBuffer.texture.enableNearestScaling(); + this.frameBuffer = GLFramebuffer.createRGBA(gl, 100, 100); + + if( this.scaleMode === CONST.SCALE_MODES.NEAREST) + { + this.frameBuffer.texture.enableNearestScaling(); + } + else + { + this.frameBuffer.texture.enableLinearScaling(); + + } + /* + A frame buffer needs a target to render to.. + create a texture and bind it attach it to the framebuffer.. + */ + + // this is used by the base texture + this.texture = this.frameBuffer.texture; } else { - this.frameBuffer.texture.enableLinearScaling(); + // make it a null framebuffer.. + this.frameBuffer = new GLFramebuffer(gl, 100, 100); + this.frameBuffer.framebuffer = null; } - /* - A frame buffer needs a target to render to.. - create a texture and bind it attach it to the framebuffer.. - */ - // this is used by the base texture - this.texture = this.frameBuffer.texture; - } - else - { - // make it a null framebuffer.. - this.frameBuffer = new GLFramebuffer(gl, 100, 100); - this.frameBuffer.framebuffer = null; + this.setFrame(); + this.resize(width, height); } - this.setFrame(); - - this.resize(width, height); -}; - -RenderTarget.prototype.constructor = RenderTarget; -module.exports = RenderTarget; - -/** - * Clears the filter texture. - * - * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer - */ -RenderTarget.prototype.clear = function(clearColor) -{ - var cc = clearColor || this.clearColor; - this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); -}; - -/** - * Binds the stencil buffer. - * - */ -RenderTarget.prototype.attachStencilBuffer = function() -{ - //TODO check if stencil is done? /** - * The stencil buffer is used for masking in pixi - * lets create one and then add attach it to the framebuffer.. + * Clears the filter texture. + * + * @param [clearColor=this.clearColor] {number[]} Array of [r,g,b,a] to clear the framebuffer */ - if (!this.root) + clear(clearColor) { - this.frameBuffer.enableStencil(); - } -}; - -RenderTarget.prototype.setFrame = function(destinationFrame, sourceFrame) -{ - this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; - this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; -}; - -/** - * Binds the buffers and initialises the viewport. - * - */ -RenderTarget.prototype.activate = function() -{ - //TOOD refactor usage of frame.. - var gl = this.gl; - - // make surethe texture is unbound! - this.frameBuffer.bind(); - - this.calculateProjection( this.destinationFrame, this.sourceFrame ); - - if(this.transform) - { - this.projectionMatrix.append(this.transform); + var cc = clearColor || this.clearColor; + this.frameBuffer.clear(cc[0],cc[1],cc[2],cc[3]);//r,g,b,a); } - //TODO add a check as them may be the same! - if(this.destinationFrame !== this.sourceFrame) + /** + * Binds the stencil buffer. + * + */ + attachStencilBuffer() { - - gl.enable(gl.SCISSOR_TEST); - gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + //TODO check if stencil is done? + /** + * The stencil buffer is used for masking in pixi + * lets create one and then add attach it to the framebuffer.. + */ + if (!this.root) + { + this.frameBuffer.enableStencil(); + } } - else + + setFrame(destinationFrame, sourceFrame) { - gl.disable(gl.SCISSOR_TEST); + this.destinationFrame = destinationFrame || this.destinationFrame || this.defaultFrame; + this.sourceFrame = sourceFrame || this.sourceFrame || destinationFrame; + } + + /** + * Binds the buffers and initialises the viewport. + * + */ + activate() + { + //TOOD refactor usage of frame.. + var gl = this.gl; + + // make surethe texture is unbound! + this.frameBuffer.bind(); + + this.calculateProjection( this.destinationFrame, this.sourceFrame ); + + if(this.transform) + { + this.projectionMatrix.append(this.transform); + } + + //TODO add a check as them may be the same! + if(this.destinationFrame !== this.sourceFrame) + { + + gl.enable(gl.SCISSOR_TEST); + gl.scissor(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height* this.resolution) | 0); + } + else + { + gl.disable(gl.SCISSOR_TEST); + } + + + // TODO - does not need to be updated all the time?? + gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); + + } - // TODO - does not need to be updated all the time?? - gl.viewport(this.destinationFrame.x | 0,this.destinationFrame.y | 0, (this.destinationFrame.width * this.resolution) | 0, (this.destinationFrame.height * this.resolution)|0); - - -}; - - -/** - * Updates the projection matrix based on a projection frame (which is a rectangle) - * - */ -RenderTarget.prototype.calculateProjection = function (destinationFrame, sourceFrame) -{ - var pm = this.projectionMatrix; - - sourceFrame = sourceFrame || destinationFrame; - - pm.identity(); - - // TODO: make dest scale source - if (!this.root) + /** + * Updates the projection matrix based on a projection frame (which is a rectangle) + * + */ + calculateProjection(destinationFrame, sourceFrame) { - pm.a = 1 / destinationFrame.width*2; - pm.d = 1 / destinationFrame.height*2; + var pm = this.projectionMatrix; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = -1 - sourceFrame.y * pm.d; - } - else - { - pm.a = 1 / destinationFrame.width*2; - pm.d = -1 / destinationFrame.height*2; + sourceFrame = sourceFrame || destinationFrame; - pm.tx = -1 - sourceFrame.x * pm.a; - pm.ty = 1 - sourceFrame.y * pm.d; - } -}; + pm.identity(); + // TODO: make dest scale source + if (!this.root) + { + pm.a = 1 / destinationFrame.width*2; + pm.d = 1 / destinationFrame.height*2; -/** - * Resizes the texture to the specified width and height - * - * @param width {Number} the new width of the texture - * @param height {Number} the new height of the texture - */ -RenderTarget.prototype.resize = function (width, height) -{ - width = width | 0; - height = height | 0; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = -1 - sourceFrame.y * pm.d; + } + else + { + pm.a = 1 / destinationFrame.width*2; + pm.d = -1 / destinationFrame.height*2; - if (this.size.width === width && this.size.height === height) - { - return; + pm.tx = -1 - sourceFrame.x * pm.a; + pm.ty = 1 - sourceFrame.y * pm.d; + } } - this.size.width = width; - this.size.height = height; - this.defaultFrame.width = width; - this.defaultFrame.height = height; + /** + * Resizes the texture to the specified width and height + * + * @param width {Number} the new width of the texture + * @param height {Number} the new height of the texture + */ + resize(width, height) + { + width = width | 0; + height = height | 0; + + if (this.size.width === width && this.size.height === height) + { + return; + } + + this.size.width = width; + this.size.height = height; + + this.defaultFrame.width = width; + this.defaultFrame.height = height; - this.frameBuffer.resize(width * this.resolution, height * this.resolution); + this.frameBuffer.resize(width * this.resolution, height * this.resolution); - var projectionFrame = this.frame || this.size; + var projectionFrame = this.frame || this.size; - this.calculateProjection( projectionFrame ); -}; + this.calculateProjection( projectionFrame ); + } -/** - * Destroys the render target. - * - */ -RenderTarget.prototype.destroy = function () -{ - this.frameBuffer.destroy(); + /** + * Destroys the render target. + * + */ + destroy() + { + this.frameBuffer.destroy(); - this.frameBuffer = null; - this.texture = null; -}; + this.frameBuffer = null; + this.texture = null; + } + +} + +module.exports = RenderTarget; diff --git a/src/core/sprites/Sprite.js b/src/core/sprites/Sprite.js index fe30bb2..9453103 100644 --- a/src/core/sprites/Sprite.js +++ b/src/core/sprites/Sprite.js @@ -19,101 +19,418 @@ * @memberof PIXI * @param texture {PIXI.Texture} The texture for this sprite */ -function Sprite(texture) -{ - Container.call(this); +class Sprite extends Container { + constructor(texture) + { + super(); + + /** + * The anchor sets the origin point of the texture. + * The default is 0,0 this means the texture's origin is the top left + * Setting the anchor to 0.5,0.5 means the texture's origin is centered + * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * + * @member {PIXI.ObservablePoint} + */ + this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); + + /** + * The texture that the sprite is using + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The width of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._width = 0; + + /** + * The height of the sprite (this is initially set by the texture) + * + * @member {number} + * @private + */ + this._height = 0; + + /** + * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * + * @member {number} + * @default 0xFFFFFF + */ + this._tint = null; + this._tintRGB = null; + this.tint = 0xFFFFFF; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = CONST.BLEND_MODES.NORMAL; + + /** + * The shader that will be used to render the sprite. Set to null to remove a current shader. + * + * @member {PIXI.AbstractFilter|PIXI.Shader} + */ + this.shader = null; + + /** + * An internal cached value of the tint. + * + * @member {number} + * @default 0xFFFFFF + * @private + */ + this.cachedTint = 0xFFFFFF; + + // call texture setter + this.texture = texture || Texture.EMPTY; + + /** + * this is used to store the vertex data of the sprite (basically a quad) + * @type {Float32Array} + */ + this.vertexData = new Float32Array(8); + + /** + * this is used to calculate the bounds of the object IF it is a trimmed sprite + * @type {Float32Array} + */ + this.vertexTrimmedData = null; + + this._transformID = -1; + this._textureID = -1; + } /** - * The anchor sets the origin point of the texture. - * The default is 0,0 this means the texture's origin is the top left - * Setting the anchor to 0.5,0.5 means the texture's origin is centered - * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner + * When the texture is updated, this event will fire to update the scale and frame * - * @member {PIXI.ObservablePoint} - */ - this.anchor = new math.ObservablePoint(this.onAnchorUpdate, this); - - /** - * The texture that the sprite is using - * - * @member {PIXI.Texture} * @private */ - this._texture = null; + _onTextureUpdate() + { + this._textureID = -1; + + // so if _width is 0 then width was not set.. + if (this._width) + { + this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; + } + + if (this._height) + { + this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; + } + } + + onAnchorUpdate() + { + this._transformID = -1; + } /** - * The width of the sprite (this is initially set by the texture) + * calculates worldTransform * vertices, store it in vertexData + */ + calculateVertices() + { + if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) + { + return; + } + + this._transformID = this.transform._worldID; + this._textureID = this._texture._updateID; + + // set the vertex data + + var texture = this._texture, + wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + vertexData = this.vertexData, + w0, w1, h0, h1, + trim = texture.trim, + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - this.anchor._x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - this.anchor._y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = orig.width * (1-this.anchor._x); + w1 = orig.width * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + } + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData + * This is used to ensure that the true width and height of a trimmed texture is respected + */ + calculateTrimmedVertices() + { + if(!this.vertexTrimmedData) + { + this.vertexTrimmedData = new Float32Array(8); + } + + // lets do some special trim code! + var texture = this._texture, + vertexData = this.vertexTrimmedData, + orig = texture.orig; + + // lets calculate the new untrimmed bounds.. + var wt = this.transform.worldTransform, + a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, + w0, w1, h0, h1; + + w0 = (orig.width ) * (1-this.anchor._x); + w1 = (orig.width ) * -this.anchor._x; + + h0 = orig.height * (1-this.anchor._y); + h1 = orig.height * -this.anchor._y; + + // xy + vertexData[0] = a * w1 + c * h1 + tx; + vertexData[1] = d * h1 + b * w1 + ty; + + // xy + vertexData[2] = a * w0 + c * h1 + tx; + vertexData[3] = d * h1 + b * w0 + ty; + + // xy + vertexData[4] = a * w0 + c * h0 + tx; + vertexData[5] = d * h0 + b * w0 + ty; + + // xy + vertexData[6] = a * w1 + c * h0 + tx; + vertexData[7] = d * h0 + b * w1 + ty; + } + + /** + * + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} + * @private + */ + _renderWebGL(renderer) + { + this.calculateVertices(); + + renderer.setObjectRenderer(renderer.plugins.sprite); + renderer.plugins.sprite.render(this); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + renderer.plugins.sprite.render(this); + } + + + _calculateBounds() + { + + var trim = this._texture.trim, + orig = this._texture.orig; + + //First lets check to see if the current texture has a trim.. + if (!trim || trim.width === orig.width && trim.height === orig.height) { + + // no trim! lets use the usual calculations.. + this.calculateVertices(); + this._bounds.addQuad(this.vertexData); + } + else + { + // lets calculate a special trimmed bounds... + this.calculateTrimmedVertices(); + this._bounds.addQuad(this.vertexTrimmedData); + } + } + + /** + * Gets the local bounds of the sprite object. * - * @member {number} - * @private */ - this._width = 0; + + getLocalBounds(rect) + { + // we can do a fast local bounds if the sprite has no children! + if(this.children.length === 0) + { + + this._bounds.minX = -this._texture.orig.width * this.anchor._x; + this._bounds.minY = -this._texture.orig.height * this.anchor._y; + this._bounds.maxX = this._texture.orig.width; + this._bounds.maxY = this._texture.orig.height; + + if(!rect) + { + if(!this._localBoundsRect) + { + this._localBoundsRect = new math.Rectangle(); + } + + rect = this._localBoundsRect; + } + + return this._bounds.getRectangle(rect); + } + else + { + return Container.prototype.getLocalBounds.call(this, rect); + } + + } /** - * The height of the sprite (this is initially set by the texture) + * Tests if a point is inside this sprite + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._texture.orig.width; + var height = this._texture.orig.height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + + /** + * Destroys this sprite and optionally its texture and children * - * @member {number} - * @private + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well */ - this._height = 0; + destroy(options) + { + super.destroy(options); + + this.anchor = null; + + var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; + if (destroyTexture) + { + var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; + this._texture.destroy(!!destroyBaseTexture); + } + + this._texture = null; + this.shader = null; + } + + // some helper functions.. /** - * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect. + * Helper function that creates a new sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture * - * @member {number} - * @default 0xFFFFFF + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture */ - this._tint = null; - this._tintRGB = null; - this.tint = 0xFFFFFF; + static from(source) + { + return new Sprite(Texture.from(source)); + } /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId */ - this.blendMode = CONST.BLEND_MODES.NORMAL; + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return new Sprite(texture); + } /** - * The shader that will be used to render the sprite. Set to null to remove a current shader. + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded * - * @member {PIXI.AbstractFilter|PIXI.Shader} + * @static + * @param imageId {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id */ - this.shader = null; + static fromImage(imageId, crossorigin, scaleMode) + { + return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); + } - /** - * An internal cached value of the tint. - * - * @member {number} - * @default 0xFFFFFF - * @private - */ - this.cachedTint = 0xFFFFFF; - - // call texture setter - this.texture = texture || Texture.EMPTY; - - /** - * this is used to store the vertex data of the sprite (basically a quad) - * @type {Float32Array} - */ - this.vertexData = new Float32Array(8); - - /** - * this is used to calculate the bounds of the object IF it is a trimmed sprite - * @type {Float32Array} - */ - this.vertexTrimmedData = null; - - this._transformID = -1; - this._textureID = -1; } -// constructor -Sprite.prototype = Object.create(Container.prototype); -Sprite.prototype.constructor = Sprite; module.exports = Sprite; Object.defineProperties(Sprite.prototype, { @@ -205,320 +522,3 @@ } } }); - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Sprite.prototype._onTextureUpdate = function () -{ - this._textureID = -1; - - // so if _width is 0 then width was not set.. - if (this._width) - { - this.scale.x = utils.sign(this.scale.x) * this._width / this.texture.orig.width; - } - - if (this._height) - { - this.scale.y = utils.sign(this.scale.y) * this._height / this.texture.orig.height; - } -}; - -Sprite.prototype.onAnchorUpdate = function() -{ - this._transformID = -1; -}; - -/** - * calculates worldTransform * vertices, store it in vertexData - */ -Sprite.prototype.calculateVertices = function () -{ - if(this._transformID === this.transform._worldID && this._textureID === this._texture._updateID) - { - return; - } - - this._transformID = this.transform._worldID; - this._textureID = this._texture._updateID; - - // set the vertex data - - var texture = this._texture, - wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - vertexData = this.vertexData, - w0, w1, h0, h1, - trim = texture.trim, - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - this.anchor._x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - this.anchor._y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = orig.width * (1-this.anchor._x); - w1 = orig.width * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - } - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** - * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData - * This is used to ensure that the true width and height of a trimmed texture is respected - */ -Sprite.prototype.calculateTrimmedVertices = function () -{ - if(!this.vertexTrimmedData) - { - this.vertexTrimmedData = new Float32Array(8); - } - - // lets do some special trim code! - var texture = this._texture, - vertexData = this.vertexTrimmedData, - orig = texture.orig; - - // lets calculate the new untrimmed bounds.. - var wt = this.transform.worldTransform, - a = wt.a, b = wt.b, c = wt.c, d = wt.d, tx = wt.tx, ty = wt.ty, - w0, w1, h0, h1; - - w0 = (orig.width ) * (1-this.anchor._x); - w1 = (orig.width ) * -this.anchor._x; - - h0 = orig.height * (1-this.anchor._y); - h1 = orig.height * -this.anchor._y; - - // xy - vertexData[0] = a * w1 + c * h1 + tx; - vertexData[1] = d * h1 + b * w1 + ty; - - // xy - vertexData[2] = a * w0 + c * h1 + tx; - vertexData[3] = d * h1 + b * w0 + ty; - - // xy - vertexData[4] = a * w0 + c * h0 + tx; - vertexData[5] = d * h0 + b * w0 + ty; - - // xy - vertexData[6] = a * w1 + c * h0 + tx; - vertexData[7] = d * h0 + b * w1 + ty; -}; - -/** -* -* Renders the object using the WebGL renderer -* -* @param renderer {PIXI.WebGLRenderer} -* @private -*/ -Sprite.prototype._renderWebGL = function (renderer) -{ - this.calculateVertices(); - - renderer.setObjectRenderer(renderer.plugins.sprite); - renderer.plugins.sprite.render(this); -}; - -/** -* Renders the object using the Canvas renderer -* -* @param renderer {PIXI.CanvasRenderer} The renderer -* @private -*/ -Sprite.prototype._renderCanvas = function (renderer) -{ - renderer.plugins.sprite.render(this); -}; - - -Sprite.prototype._calculateBounds = function () -{ - - var trim = this._texture.trim, - orig = this._texture.orig; - - //First lets check to see if the current texture has a trim.. - if (!trim || trim.width === orig.width && trim.height === orig.height) { - - // no trim! lets use the usual calculations.. - this.calculateVertices(); - this._bounds.addQuad(this.vertexData); - } - else - { - // lets calculate a special trimmed bounds... - this.calculateTrimmedVertices(); - this._bounds.addQuad(this.vertexTrimmedData); - } -}; - -/** - * Gets the local bounds of the sprite object. - * - */ - -Sprite.prototype.getLocalBounds = function (rect) -{ - // we can do a fast local bounds if the sprite has no children! - if(this.children.length === 0) - { - - this._bounds.minX = -this._texture.orig.width * this.anchor._x; - this._bounds.minY = -this._texture.orig.height * this.anchor._y; - this._bounds.maxX = this._texture.orig.width; - this._bounds.maxY = this._texture.orig.height; - - if(!rect) - { - if(!this._localBoundsRect) - { - this._localBoundsRect = new math.Rectangle(); - } - - rect = this._localBoundsRect; - } - - return this._bounds.getRectangle(rect); - } - else - { - return Container.prototype.getLocalBounds.call(this, rect); - } - -}; - -/** -* Tests if a point is inside this sprite -* -* @param point {PIXI.Point} the point to test -* @return {boolean} the result of the test -*/ -Sprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._texture.orig.width; - var height = this._texture.orig.height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - - -/** - * Destroys this sprite and optionally its texture and children - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=false] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=false] {boolean} Should it destroy the base texture of the sprite as well - */ -Sprite.prototype.destroy = function (options) -{ - Container.prototype.destroy.call(this, options); - - this.anchor = null; - - var destroyTexture = typeof options === 'boolean' ? options : options && options.texture; - if (destroyTexture) - { - var destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture; - this._texture.destroy(!!destroyBaseTexture); - } - - this._texture = null; - this.shader = null; -}; - -// some helper functions.. - -/** - * Helper function that creates a new sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Sprite.from = function (source) -{ - return new Sprite(Texture.from(source)); -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId - */ -Sprite.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return new Sprite(texture); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id - */ -Sprite.fromImage = function (imageId, crossorigin, scaleMode) -{ - return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode)); -}; diff --git a/src/core/sprites/canvas/CanvasSpriteRenderer.js b/src/core/sprites/canvas/CanvasSpriteRenderer.js index a475f81..929180d 100644 --- a/src/core/sprites/canvas/CanvasSpriteRenderer.js +++ b/src/core/sprites/canvas/CanvasSpriteRenderer.js @@ -24,141 +24,142 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer sprite this batch works for. */ -function CanvasSpriteRenderer(renderer) -{ - this.renderer = renderer; +class CanvasSpriteRenderer { + constructor(renderer) + { + this.renderer = renderer; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + var texture = sprite._texture, + renderer = this.renderer, + wt = sprite.transform.worldTransform, + dx, + dy, + width = texture._frame.width, + height = texture._frame.height; + + if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) + { + return; + } + + renderer.setBlendMode(sprite.blendMode); + + // Ignore null sources + if (texture.valid) + { + renderer.context.globalAlpha = sprite.worldAlpha; + + // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture + var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; + if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) + { + renderer.context[renderer.smoothProperty] = smoothingEnabled; + } + + if (texture.trim) { + dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; + dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; + } else { + dx = (0.5 - sprite.anchor.x) * texture.orig.width; + dy = (0.5 - sprite.anchor.y) * texture.orig.height; + } + if(texture.rotate) { + wt.copy(canvasRenderWorldTransform); + wt = canvasRenderWorldTransform; + math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); + // the anchor has already been applied above, so lets set it to zero + dx = 0; + dy = 0; + } + dx -= width/2; + dy -= height/2; + // Allow for pixel rounding + if (renderer.roundPixels) + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + (wt.tx * renderer.resolution) | 0, + (wt.ty * renderer.resolution) | 0 + ); + + dx = dx | 0; + dy = dy | 0; + } + else + { + renderer.context.setTransform( + wt.a, + wt.b, + wt.c, + wt.d, + wt.tx * renderer.resolution, + wt.ty * renderer.resolution + ); + } + + var resolution = texture.baseTexture.resolution; + + if (sprite.tint !== 0xFFFFFF) + { + if (sprite.cachedTint !== sprite.tint) + { + sprite.cachedTint = sprite.tint; + + // TODO clean up caching - how to clean up the caches? + sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); + } + + renderer.context.drawImage( + sprite.tintedTexture, + 0, + 0, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + else + { + + renderer.context.drawImage( + texture.baseTexture.source, + texture._frame.x * resolution, + texture._frame.y * resolution, + width * resolution, + height * resolution, + dx * renderer.resolution, + dy * renderer.resolution, + width * renderer.resolution, + height * renderer.resolution + ); + } + } + } + + /** + * destroy the sprite object. + * + */ + destroy() { + this.renderer = null; + } + } - -CanvasSpriteRenderer.prototype.constructor = CanvasSpriteRenderer; module.exports = CanvasSpriteRenderer; CanvasRenderer.registerPlugin('sprite', CanvasSpriteRenderer); - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -CanvasSpriteRenderer.prototype.render = function (sprite) -{ - var texture = sprite._texture, - renderer = this.renderer, - wt = sprite.transform.worldTransform, - dx, - dy, - width = texture._frame.width, - height = texture._frame.height; - - if (texture.orig.width <= 0 || texture.orig.height <= 0 || !texture.baseTexture.source) - { - return; - } - - renderer.setBlendMode(sprite.blendMode); - - // Ignore null sources - if (texture.valid) - { - renderer.context.globalAlpha = sprite.worldAlpha; - - // If smoothingEnabled is supported and we need to change the smoothing property for sprite texture - var smoothingEnabled = texture.baseTexture.scaleMode === CONST.SCALE_MODES.LINEAR; - if (renderer.smoothProperty && renderer.context[renderer.smoothProperty] !== smoothingEnabled) - { - renderer.context[renderer.smoothProperty] = smoothingEnabled; - } - - if (texture.trim) { - dx = texture.trim.width/2 + texture.trim.x - sprite.anchor.x * texture.orig.width; - dy = texture.trim.height/2 + texture.trim.y - sprite.anchor.y * texture.orig.height; - } else { - dx = (0.5 - sprite.anchor.x) * texture.orig.width; - dy = (0.5 - sprite.anchor.y) * texture.orig.height; - } - if(texture.rotate) { - wt.copy(canvasRenderWorldTransform); - wt = canvasRenderWorldTransform; - math.GroupD8.matrixAppendRotationInv(wt, texture.rotate, dx, dy); - // the anchor has already been applied above, so lets set it to zero - dx = 0; - dy = 0; - } - dx -= width/2; - dy -= height/2; - // Allow for pixel rounding - if (renderer.roundPixels) - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - (wt.tx * renderer.resolution) | 0, - (wt.ty * renderer.resolution) | 0 - ); - - dx = dx | 0; - dy = dy | 0; - } - else - { - renderer.context.setTransform( - wt.a, - wt.b, - wt.c, - wt.d, - wt.tx * renderer.resolution, - wt.ty * renderer.resolution - ); - } - - var resolution = texture.baseTexture.resolution; - - if (sprite.tint !== 0xFFFFFF) - { - if (sprite.cachedTint !== sprite.tint) - { - sprite.cachedTint = sprite.tint; - - // TODO clean up caching - how to clean up the caches? - sprite.tintedTexture = CanvasTinter.getTintedTexture(sprite, sprite.tint); - } - - renderer.context.drawImage( - sprite.tintedTexture, - 0, - 0, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - else - { - - renderer.context.drawImage( - texture.baseTexture.source, - texture._frame.x * resolution, - texture._frame.y * resolution, - width * resolution, - height * resolution, - dx * renderer.resolution, - dy * renderer.resolution, - width * renderer.resolution, - height * renderer.resolution - ); - } - } -}; - -/** - * destroy the sprite object. - * - */ -CanvasSpriteRenderer.prototype.destroy = function (){ - this.renderer = null; -}; diff --git a/src/core/sprites/webgl/SpriteRenderer.js b/src/core/sprites/webgl/SpriteRenderer.js index 2ef4591..9e4b8b7 100644 --- a/src/core/sprites/webgl/SpriteRenderer.js +++ b/src/core/sprites/webgl/SpriteRenderer.js @@ -18,404 +18,406 @@ * @extends PIXI.ObjectRenderer * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function SpriteRenderer(renderer) -{ - ObjectRenderer.call(this, renderer); - - /** - * Number of values sent in the vertex buffer. - * positionX, positionY, colorR, colorG, colorB = 5 - * - * @member {number} - */ - this.vertSize = 5; - - /** - * The size of the vertex information in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of images in the SpriteBatch before it flushes. - * - * @member {number} - */ - this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - - // the total number of bytes in our batch - // var numVerts = this.size * 4 * this.vertByteSize; - - this.buffers = []; - for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { - var numVertsTemp = i * 4 * this.vertByteSize; - this.buffers.push(new Buffer(numVertsTemp)); - } - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - - /** - * The default shaders that is used if a sprite doesn't have a more specific one. - * there is a shader for each number of textures that can be rendererd. - * These shaders will also be generated on the fly as required. - * @member {PIXI.Shader[]} - */ - this.shaders = null; - - this.currentIndex = 0; - TICK =0; - this.groups = []; - - for (var k = 0; k < this.size; k++) +class SpriteRenderer extends ObjectRenderer { + constructor(renderer) { - this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + super(renderer); + + /** + * Number of values sent in the vertex buffer. + * positionX, positionY, colorR, colorG, colorB = 5 + * + * @member {number} + */ + this.vertSize = 5; + + /** + * The size of the vertex information in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of images in the SpriteBatch before it flushes. + * + * @member {number} + */ + this.size = CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + + // the total number of bytes in our batch + // var numVerts = this.size * 4 * this.vertByteSize; + + this.buffers = []; + for (var i = 1; i <= bitTwiddle.nextPow2(this.size); i*=2) { + var numVertsTemp = i * 4 * this.vertByteSize; + this.buffers.push(new Buffer(numVertsTemp)); + } + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + + /** + * The default shaders that is used if a sprite doesn't have a more specific one. + * there is a shader for each number of textures that can be rendererd. + * These shaders will also be generated on the fly as required. + * @member {PIXI.Shader[]} + */ + this.shaders = null; + + this.currentIndex = 0; + TICK =0; + this.groups = []; + + for (var k = 0; k < this.size; k++) + { + this.groups[k] = {textures:[], textureCount:0, ids:[], size:0, start:0, blend:0}; + } + + this.sprites = []; + + this.vertexBuffers = []; + this.vaos = []; + + this.vaoMax = 2; + this.vertexCount = 0; + + this.renderer.on('prerender', this.onPrerender, this); } - this.sprites = []; + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + onContextChange() + { + var gl = this.renderer.gl; - this.vertexBuffers = []; - this.vaos = []; + // step 1: first check max textures the GPU can handle. + this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - this.vaoMax = 2; - this.vertexCount = 0; + // step 2: check the maximum number of if statements the shader can have too.. + this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - this.renderer.on('prerender', this.onPrerender, this); + this.shaders = new Array(this.MAX_TEXTURES); + this.shaders[0] = generateMultiTextureShader(gl, 1); + this.shaders[1] = generateMultiTextureShader(gl, 2); + + // create a couple of buffers + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + // we use the second shader as the first one depending on your browser may omit aTextureId + // as it is not used by the shader so is optimized out. + var shader = this.shaders[1]; + + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + + // build the vao object that will render.. + this.vaos[i] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vao = this.vaos[0]; + this.currentBlendMode = 99999; + } + + onPrerender() + { + this.vertexCount = 0; + } + + /** + * Renders the sprite object. + * + * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch + */ + render(sprite) + { + //TODO set blend modes.. + // check texture.. + if (this.currentIndex >= this.size) + { + this.flush(); + } + + + // get the uvs for the texture + + + // if the uvs have not updated then no point rendering just yet! + if (!sprite.texture._uvs) + { + return; + } + + // push a texture. + // increment the batchsize + this.sprites[this.currentIndex++] = sprite; + } + + /** + * Renders the content and empties the current batch. + * + */ + flush() + { + if (this.currentIndex === 0) { + return; + } + + var gl = this.renderer.gl; + + var np2 = bitTwiddle.nextPow2(this.currentIndex); + var log2 = bitTwiddle.log2(np2); + var buffer = this.buffers[log2]; + + var sprites = this.sprites; + var groups = this.groups; + + var float32View = buffer.float32View; + var uint32View = buffer.uint32View; + + var index = 0; + var nextTexture; + var currentTexture; + var groupCount = 1; + var textureCount = 0; + var currentGroup = groups[0]; + var vertexData; + var tint; + var uvs; + var textureId; + var blendMode = sprites[0].blendMode; + var shader; + + currentGroup.textureCount = 0; + currentGroup.start = 0; + currentGroup.blend = blendMode; + + TICK++; + + for (var i = 0; i < this.currentIndex; i++) + { + // upload the sprite elemetns... + // they have all ready been calculated so we just need to push them into the buffer. + var sprite = sprites[i]; + + nextTexture = sprite._texture.baseTexture; + + if(blendMode !== sprite.blendMode) + { + blendMode = sprite.blendMode; + + // force the batch to break! + currentTexture = null; + textureCount = this.MAX_TEXTURES; + TICK++; + } + + if(currentTexture !== nextTexture) + { + currentTexture = nextTexture; + + if(nextTexture._enabled !== TICK) + { + if(textureCount === this.MAX_TEXTURES) + { + TICK++; + + textureCount = 0; + + currentGroup.size = i - currentGroup.start; + + currentGroup = groups[groupCount++]; + currentGroup.textureCount = 0; + currentGroup.blend = blendMode; + currentGroup.start = i; + } + + nextTexture._enabled = TICK; + nextTexture._id = textureCount; + + currentGroup.textures[currentGroup.textureCount++] = nextTexture; + textureCount++; + } + + } + + vertexData = sprite.vertexData; + + //TODO this sum does not need to be set each frame.. + tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); + uvs = sprite._texture._uvs.uvsUint32; + textureId = nextTexture._id; + + if (this.renderer.roundPixels) + { + var resolution = this.renderer.resolution; + + //xy + float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; + float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; + + // xy + float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; + float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; + + // xy + float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; + float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; + + // xy + float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; + float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; + + } + else + { + //xy + float32View[index] = vertexData[0]; + float32View[index+1] = vertexData[1]; + + // xy + float32View[index+5] = vertexData[2]; + float32View[index+6] = vertexData[3]; + + // xy + float32View[index+10] = vertexData[4]; + float32View[index+11] = vertexData[5]; + + // xy + float32View[index+15] = vertexData[6]; + float32View[index+16] = vertexData[7]; + } + + uint32View[index+2] = uvs[0]; + uint32View[index+7] = uvs[1]; + uint32View[index+12] = uvs[2]; + uint32View[index+17] = uvs[3]; + + uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; + float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; + + index += 20; + } + + currentGroup.size = i - currentGroup.start; + + this.vertexCount++; + + if(this.vaoMax <= this.vertexCount) + { + this.vaoMax++; + shader = this.shaders[1]; + this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); + // build the vao object that will render.. + this.vaos[this.vertexCount] = this.renderer.createVao() + .addIndex(this.indexBuffer) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) + .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); + } + + this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); + this.vao = this.vaos[this.vertexCount].bind(); + + /// render the groups.. + for (i = 0; i < groupCount; i++) { + + var group = groups[i]; + var groupTextureCount = group.textureCount; + shader = this.shaders[groupTextureCount-1]; + + if(!shader) + { + shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); + //console.log("SHADER generated for " + textureCount + " textures") + } + + this.renderer.bindShader(shader); + + for (var j = 0; j < groupTextureCount; j++) + { + this.renderer.bindTexture(group.textures[j], j); + } + + // set the blend mode.. + this.renderer.state.setBlendMode( group.blend ); + + gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); + } + + // reset elements for the next flush + this.currentIndex = 0; + } + + /** + * Starts a new sprite batch. + * + */ + start() + { + //this.renderer.bindShader(this.shader); + //TICK %= 1000; + } + + stop() + { + this.flush(); + this.vao.unbind(); + } + + /** + * Destroys the SpriteBatch. + * + */ + destroy() + { + for (var i = 0; i < this.vaoMax; i++) { + this.vertexBuffers[i].destroy(); + this.vaos[i].destroy(); + } + + this.indexBuffer.destroy(); + + this.renderer.off('prerender', this.onPrerender, this); + + super.destroy(); + + for (i = 0; i < this.shaders.length; i++) { + + if(this.shaders[i]) + { + this.shaders[i].destroy(); + } + } + + this.vertexBuffers = null; + this.vaos = null; + this.indexBuffer = null; + this.indices = null; + + this.sprites = null; + + for (i = 0; i < this.buffers.length; i++) { + this.buffers[i].destroy(); + } + + } + } - -SpriteRenderer.prototype = Object.create(ObjectRenderer.prototype); -SpriteRenderer.prototype.constructor = SpriteRenderer; module.exports = SpriteRenderer; WebGLRenderer.registerPlugin('sprite', SpriteRenderer); - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -SpriteRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - // step 1: first check max textures the GPU can handle. - this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), CONST.SPRITE_MAX_TEXTURES); - - // step 2: check the maximum number of if statements the shader can have too.. - this.MAX_TEXTURES = checkMaxIfStatmentsInShader( this.MAX_TEXTURES, gl ); - - this.shaders = new Array(this.MAX_TEXTURES); - this.shaders[0] = generateMultiTextureShader(gl, 1); - this.shaders[1] = generateMultiTextureShader(gl, 2); - - // create a couple of buffers - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - // we use the second shader as the first one depending on your browser may omit aTextureId - // as it is not used by the shader so is optimized out. - var shader = this.shaders[1]; - - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - - // build the vao object that will render.. - this.vaos[i] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vao = this.vaos[0]; - this.currentBlendMode = 99999; -}; - -SpriteRenderer.prototype.onPrerender = function () -{ - this.vertexCount = 0; -}; - -/** - * Renders the sprite object. - * - * @param sprite {PIXI.Sprite} the sprite to render when using this spritebatch - */ -SpriteRenderer.prototype.render = function (sprite) -{ - //TODO set blend modes.. - // check texture.. - if (this.currentIndex >= this.size) - { - this.flush(); - } - - - // get the uvs for the texture - - - // if the uvs have not updated then no point rendering just yet! - if (!sprite.texture._uvs) - { - return; - } - - // push a texture. - // increment the batchsize - this.sprites[this.currentIndex++] = sprite; -}; - -/** - * Renders the content and empties the current batch. - * - */ -SpriteRenderer.prototype.flush = function () -{ - if (this.currentIndex === 0) { - return; - } - - var gl = this.renderer.gl; - - var np2 = bitTwiddle.nextPow2(this.currentIndex); - var log2 = bitTwiddle.log2(np2); - var buffer = this.buffers[log2]; - - var sprites = this.sprites; - var groups = this.groups; - - var float32View = buffer.float32View; - var uint32View = buffer.uint32View; - - var index = 0; - var nextTexture; - var currentTexture; - var groupCount = 1; - var textureCount = 0; - var currentGroup = groups[0]; - var vertexData; - var tint; - var uvs; - var textureId; - var blendMode = sprites[0].blendMode; - var shader; - - currentGroup.textureCount = 0; - currentGroup.start = 0; - currentGroup.blend = blendMode; - - TICK++; - - for (var i = 0; i < this.currentIndex; i++) - { - // upload the sprite elemetns... - // they have all ready been calculated so we just need to push them into the buffer. - var sprite = sprites[i]; - - nextTexture = sprite._texture.baseTexture; - - if(blendMode !== sprite.blendMode) - { - blendMode = sprite.blendMode; - - // force the batch to break! - currentTexture = null; - textureCount = this.MAX_TEXTURES; - TICK++; - } - - if(currentTexture !== nextTexture) - { - currentTexture = nextTexture; - - if(nextTexture._enabled !== TICK) - { - if(textureCount === this.MAX_TEXTURES) - { - TICK++; - - textureCount = 0; - - currentGroup.size = i - currentGroup.start; - - currentGroup = groups[groupCount++]; - currentGroup.textureCount = 0; - currentGroup.blend = blendMode; - currentGroup.start = i; - } - - nextTexture._enabled = TICK; - nextTexture._id = textureCount; - - currentGroup.textures[currentGroup.textureCount++] = nextTexture; - textureCount++; - } - - } - - vertexData = sprite.vertexData; - - //TODO this sum does not need to be set each frame.. - tint = sprite._tintRGB + (sprite.worldAlpha * 255 << 24); - uvs = sprite._texture._uvs.uvsUint32; - textureId = nextTexture._id; - - if (this.renderer.roundPixels) - { - var resolution = this.renderer.resolution; - - //xy - float32View[index] = ((vertexData[0] * resolution) | 0) / resolution; - float32View[index+1] = ((vertexData[1] * resolution) | 0) / resolution; - - // xy - float32View[index+5] = ((vertexData[2] * resolution) | 0) / resolution; - float32View[index+6] = ((vertexData[3] * resolution) | 0) / resolution; - - // xy - float32View[index+10] = ((vertexData[4] * resolution) | 0) / resolution; - float32View[index+11] = ((vertexData[5] * resolution) | 0) / resolution; - - // xy - float32View[index+15] = ((vertexData[6] * resolution) | 0) / resolution; - float32View[index+16] = ((vertexData[7] * resolution) | 0) / resolution; - - } - else - { - //xy - float32View[index] = vertexData[0]; - float32View[index+1] = vertexData[1]; - - // xy - float32View[index+5] = vertexData[2]; - float32View[index+6] = vertexData[3]; - - // xy - float32View[index+10] = vertexData[4]; - float32View[index+11] = vertexData[5]; - - // xy - float32View[index+15] = vertexData[6]; - float32View[index+16] = vertexData[7]; - } - - uint32View[index+2] = uvs[0]; - uint32View[index+7] = uvs[1]; - uint32View[index+12] = uvs[2]; - uint32View[index+17] = uvs[3]; - - uint32View[index+3] = uint32View[index+8] = uint32View[index+13] = uint32View[index+18] = tint; - float32View[index+4] = float32View[index+9] = float32View[index+14] = float32View[index+19] = textureId; - - index += 20; - } - - currentGroup.size = i - currentGroup.start; - - this.vertexCount++; - - if(this.vaoMax <= this.vertexCount) - { - this.vaoMax++; - shader = this.shaders[1]; - this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW); - // build the vao object that will render.. - this.vaos[this.vertexCount] = this.renderer.createVao() - .addIndex(this.indexBuffer) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4) - .addAttribute(this.vertexBuffers[this.vertexCount], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4); - } - - this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0); - this.vao = this.vaos[this.vertexCount].bind(); - - /// render the groups.. - for (i = 0; i < groupCount; i++) { - - var group = groups[i]; - var groupTextureCount = group.textureCount; - shader = this.shaders[groupTextureCount-1]; - - if(!shader) - { - shader = this.shaders[groupTextureCount-1] = generateMultiTextureShader(gl, groupTextureCount); - //console.log("SHADER generated for " + textureCount + " textures") - } - - this.renderer.bindShader(shader); - - for (var j = 0; j < groupTextureCount; j++) - { - this.renderer.bindTexture(group.textures[j], j); - } - - // set the blend mode.. - this.renderer.state.setBlendMode( group.blend ); - - gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2); - } - - // reset elements for the next flush - this.currentIndex = 0; -}; - -/** - * Starts a new sprite batch. - * - */ -SpriteRenderer.prototype.start = function () -{ - //this.renderer.bindShader(this.shader); - //TICK %= 1000; -}; - -SpriteRenderer.prototype.stop = function () -{ - this.flush(); - this.vao.unbind(); -}; -/** - * Destroys the SpriteBatch. - * - */ -SpriteRenderer.prototype.destroy = function () -{ - for (var i = 0; i < this.vaoMax; i++) { - this.vertexBuffers[i].destroy(); - this.vaos[i].destroy(); - } - - this.indexBuffer.destroy(); - - this.renderer.off('prerender', this.onPrerender, this); - ObjectRenderer.prototype.destroy.call(this); - - for (i = 0; i < this.shaders.length; i++) { - - if(this.shaders[i]) - { - this.shaders[i].destroy(); - } - } - - this.vertexBuffers = null; - this.vaos = null; - this.indexBuffer = null; - this.indices = null; - - this.sprites = null; - - for (i = 0; i < this.buffers.length; i++) { - this.buffers[i].destroy(); - } - -}; diff --git a/src/core/text/Text.js b/src/core/text/Text.js index 5b49553..c33bb0f 100644 --- a/src/core/text/Text.js +++ b/src/core/text/Text.js @@ -26,73 +26,644 @@ * @param text {string} The string that you would like the text to display * @param [style] {object|PIXI.TextStyle} The style parameters */ -function Text(text, style) -{ - /** - * The canvas element that everything is drawn to - * - * @member {HTMLCanvasElement} - */ - this.canvas = document.createElement('canvas'); +class Text extends Sprite { + constructor(text, style) + { + var texture = Texture.fromCanvas(document.createElement('canvas')); + texture.orig = new math.Rectangle(); + texture.trim = new math.Rectangle(); + + super(texture); + + /** + * The canvas element that everything is drawn to + * + * @member {HTMLCanvasElement} + */ + this.canvas = document.createElement('canvas'); + + /** + * The canvas 2d context that everything is drawn with + * @member {HTMLCanvasElement} + */ + this.context = this.canvas.getContext('2d'); + + /** + * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. + * @member {number} + * @default 1 + */ + this.resolution = CONST.RESOLUTION; + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = null; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._style = null; + /** + * Private listener to track style changes. + * + * @member {Function} + * @private + */ + this._styleListener = null; + + /** + * Private tracker for the current font. + * + * @member {string} + * @private + */ + this._font = ''; + + this.text = text; + this.style = style; + + this.localStyleID = -1; + } /** - * The canvas 2d context that everything is drawn with - * @member {HTMLCanvasElement} - */ - this.context = this.canvas.getContext('2d'); - - /** - * The resolution / device pixel ratio of the canvas. This is set automatically by the renderer. - * @member {number} - * @default 1 - */ - this.resolution = CONST.RESOLUTION; - - /** - * Private tracker for the current text. - * - * @member {string} + * Renders text and updates it when needed + * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. * @private */ - this._text = null; + updateText(respectDirty) + { + var style = this._style; + + // check if style has changed.. + if(this.localStyleID !== style.styleID) + { + this.dirty = true; + this.localStyleID = style.styleID; + } + + if (!this.dirty && respectDirty) { + return; + } + + // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px + var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; + this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; + + this.context.font = this._font; + + // word wrap + // preserve original text + var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; + + // split text into lines + var lines = outputText.split(/(?:\r\n|\r|\n)/); + + // calculate text width + var lineWidths = new Array(lines.length); + var maxLineWidth = 0; + var fontProperties = this.determineFontProperties(this._font); + + var i; + for (i = 0; i < lines.length; i++) + { + var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); + lineWidths[i] = lineWidth; + maxLineWidth = Math.max(maxLineWidth, lineWidth); + } + + var width = maxLineWidth + style.strokeThickness; + if (style.dropShadow) + { + width += style.dropShadowDistance; + } + + width += style.padding * 2; + + this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); + + // calculate text height + var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; + + var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; + if (style.dropShadow) + { + height += style.dropShadowDistance; + } + + this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); + + this.context.scale( this.resolution, this.resolution); + + if (navigator.isCocoonJS) + { + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + + } + + // this.context.fillStyle="#FF0000"; + // this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); + + this.context.font = this._font; + this.context.strokeStyle = style.stroke; + this.context.lineWidth = style.strokeThickness; + this.context.textBaseline = style.textBaseline; + this.context.lineJoin = style.lineJoin; + this.context.miterLimit = style.miterLimit; + + var linePositionX; + var linePositionY; + + if (style.dropShadow) + { + if (style.dropShadowBlur > 0) { + this.context.shadowColor = style.dropShadowColor; + this.context.shadowBlur = style.dropShadowBlur; + } else { + this.context.fillStyle = style.dropShadowColor; + } + + var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; + var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; + + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); + + if (style.stroke && style.strokeThickness) + { + this.context.strokeStyle = style.dropShadowColor; + this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); + this.context.strokeStyle = style.stroke; + } + } + } + } + + //set canvas text styles + this.context.fillStyle = this._generateFillStyle(style, lines); + + //draw lines line by line + for (i = 0; i < lines.length; i++) + { + linePositionX = style.strokeThickness / 2; + linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; + + if (style.align === 'right') + { + linePositionX += maxLineWidth - lineWidths[i]; + } + else if (style.align === 'center') + { + linePositionX += (maxLineWidth - lineWidths[i]) / 2; + } + + if (style.stroke && style.strokeThickness) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); + } + + if (style.fill) + { + this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); + } + } + + this.updateTexture(); + } /** - * Private tracker for the current style. - * - * @member {object} + * Render the text with letter-spacing. + * @param {string} text - The text to draw + * @param {number} x - Horizontal position to draw the text + * @param {number} y - Vertical position to draw the text + * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill * @private */ - this._style = null; - /** - * Private listener to track style changes. - * - * @member {Function} - * @private - */ - this._styleListener = null; + drawLetterSpacing(text, x, y, isStroke) + { + var style = this._style; + + // letterSpacing of 0 means normal + var letterSpacing = style.letterSpacing; + + if (letterSpacing === 0) + { + if (isStroke) + { + this.context.strokeText(text, x, y); + } + else + { + this.context.fillText(text, x, y); + } + return; + } + + var characters = String.prototype.split.call(text, ''), + index = 0, + current, + currentPosition = x; + + while (index < text.length) + { + current = characters[index++]; + if (isStroke) + { + this.context.strokeText(current, currentPosition, y); + } + else + { + this.context.fillText(current, currentPosition, y); + } + currentPosition += this.context.measureText(current).width + letterSpacing; + } + } /** - * Private tracker for the current font. + * Updates texture size based on canvas size * - * @member {string} * @private */ - this._font = ''; + updateTexture() + { + var texture = this._texture; + var style = this._style; - var texture = Texture.fromCanvas(this.canvas); - texture.orig = new math.Rectangle(); - texture.trim = new math.Rectangle(); - Sprite.call(this, texture); + texture.baseTexture.hasLoaded = true; + texture.baseTexture.resolution = this.resolution; - this.text = text; - this.style = style; + texture.baseTexture.realWidth = this.canvas.width; + texture.baseTexture.realHeight = this.canvas.height; + texture.baseTexture.width = this.canvas.width / this.resolution; + texture.baseTexture.height = this.canvas.height / this.resolution; + texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; + texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - this.localStyleID = -1; + texture.trim.x = -style.padding; + texture.trim.y = -style.padding; + + texture.orig.width = texture._frame.width- style.padding*2; + texture.orig.height = texture._frame.height - style.padding*2; + + //call sprite onTextureUpdate to update scale if _width or _height were set + this._onTextureUpdate(); + + texture.baseTexture.emit('update', texture.baseTexture); + + this.dirty = false; + } + + /** + * Renders the object using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The renderer + */ + renderWebGL(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super.renderWebGL(renderer); + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The renderer + * @private + */ + _renderCanvas(renderer) + { + if(this.resolution !== renderer.resolution) + { + this.resolution = renderer.resolution; + this.dirty = true; + } + + this.updateText(true); + + super._renderCanvas(renderer); + } + + /** + * Calculates the ascent, descent and fontSize of a given fontStyle + * + * @param fontStyle {string} String representing the style of the font + * @return {Object} Font properties object + * @private + */ + determineFontProperties(fontStyle) + { + var properties = Text.fontPropertiesCache[fontStyle]; + + if (!properties) + { + properties = {}; + + var canvas = Text.fontPropertiesCanvas; + var context = Text.fontPropertiesContext; + + context.font = fontStyle; + + var width = Math.ceil(context.measureText('|MÉq').width); + var baseline = Math.ceil(context.measureText('M').width); + var height = 2 * baseline; + + baseline = baseline * 1.4 | 0; + + canvas.width = width; + canvas.height = height; + + context.fillStyle = '#f00'; + context.fillRect(0, 0, width, height); + + context.font = fontStyle; + + context.textBaseline = 'alphabetic'; + context.fillStyle = '#000'; + context.fillText('|MÉq', 0, baseline); + + var imagedata = context.getImageData(0, 0, width, height).data; + var pixels = imagedata.length; + var line = width * 4; + + var i, j; + + var idx = 0; + var stop = false; + + // ascent. scan from top to bottom until we find a non red pixel + for (i = 0; i < baseline; i++) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx += line; + } + else + { + break; + } + } + + properties.ascent = baseline - i; + + idx = pixels - line; + stop = false; + + // descent. scan from bottom to top until we find a non red pixel + for (i = height; i > baseline; i--) + { + for (j = 0; j < line; j += 4) + { + if (imagedata[idx + j] !== 255) + { + stop = true; + break; + } + } + if (!stop) + { + idx -= line; + } + else + { + break; + } + } + + properties.descent = i - baseline; + properties.fontSize = properties.ascent + properties.descent; + + Text.fontPropertiesCache[fontStyle] = properties; + } + + return properties; + } + + /** + * Applies newlines to a string to have it optimally fit into the horizontal + * bounds set by the Text object's wordWrapWidth property. + * + * @param text {string} String to apply word wrapping to + * @return {string} New string with new lines applied where required + * @private + */ + wordWrap(text) + { + // Greedy wrapping algorithm that will wrap words as the line grows longer + // than its horizontal bounds. + var result = ''; + var lines = text.split('\n'); + var wordWrapWidth = this._style.wordWrapWidth; + for (var i = 0; i < lines.length; i++) + { + var spaceLeft = wordWrapWidth; + var words = lines[i].split(' '); + for (var j = 0; j < words.length; j++) + { + var wordWidth = this.context.measureText(words[j]).width; + if (this._style.breakWords && wordWidth > wordWrapWidth) + { + // Word should be split in the middle + var characters = words[j].split(''); + for (var c = 0; c < characters.length; c++) + { + var characterWidth = this.context.measureText(characters[c]).width; + if (characterWidth > spaceLeft) + { + result += '\n' + characters[c]; + spaceLeft = wordWrapWidth - characterWidth; + } + else + { + if (c === 0) + { + result += ' '; + } + result += characters[c]; + spaceLeft -= characterWidth; + } + } + } + else + { + var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; + if (j === 0 || wordWidthWithSpace > spaceLeft) + { + // Skip printing the newline if it's the first word of the line that is + // greater than the word wrap width. + if (j > 0) + { + result += '\n'; + } + result += words[j]; + spaceLeft = wordWrapWidth - wordWidth; + } + else + { + spaceLeft -= wordWidthWithSpace; + result += ' ' + words[j]; + } + } + } + + if (i < lines.length-1) + { + result += '\n'; + } + } + return result; + } + + /** + * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. + */ + _calculateBounds() + { + this.updateText(true); + this.calculateVertices(); + // if we have already done this on THIS frame. + this._bounds.addQuad(this.vertexData); + } + + /** + * Method to be called upon a TextStyle change. + * @private + */ + _onStyleChange() + { + this.dirty = true; + } + + /** + * Generates the fill style. Can automatically generate a gradient based on the fill style being an array + * @return string|Number|CanvasGradient + * @private + */ + _generateFillStyle(style, lines) + { + if (!Array.isArray(style.fill)) + { + return style.fill; + } + else + { + // the gradient will be evenly spaced out according to how large the array is. + // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 + var i; + var gradient; + var totalIterations; + var currentIteration; + var stop; + + var width = this.canvas.width / this.resolution; + var height = this.canvas.height / this.resolution; + + if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) + { + // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas + gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); + + // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect + // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 + totalIterations = ( style.fill.length + 1 ) * lines.length; + currentIteration = 0; + for (i = 0; i < lines.length; i++) + { + currentIteration += 1; + for (var j = 0; j < style.fill.length; j++) + { + stop = (currentIteration / totalIterations); + gradient.addColorStop(stop, style.fill[j]); + currentIteration++; + } + } + } + else + { + // start the gradient at the center left of the canvas, and end at the center right of the canvas + gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); + + // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient + totalIterations = style.fill.length + 1; + currentIteration = 1; + + for (i = 0; i < style.fill.length; i++) + { + stop = currentIteration / totalIterations; + gradient.addColorStop(stop, style.fill[i]); + currentIteration++; + } + } + + return gradient; + } + } + + /** + * Destroys this text object. + * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as + * the majorety of the time the texture will not be shared with any other Sprites. + * + * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value + * @param [options.children=false] {boolean} if set to true, all the children will have their destroy + * method called as well. 'options' will be passed on to those calls. + * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well + * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well + */ + destroy(options) + { + if (typeof options === 'boolean') { + options = { children: options }; + } + + options = Object.assign({}, defaultDestroyOptions, options); + + super.destroy(options); + + // make sure to reset the the context and canvas.. dont want this hanging around in memory! + this.context = null; + this.canvas = null; + + this._style = null; + } + } -// constructor -Text.prototype = Object.create(Sprite.prototype); -Text.prototype.constructor = Text; module.exports = Text; Text.fontPropertiesCache = {}; @@ -200,573 +771,3 @@ } } }); - -/** - * Renders text and updates it when needed - * @param respectDirty {boolean} Whether to abort updating the text if the Text isn't dirty and the function is called. - * @private - */ -Text.prototype.updateText = function (respectDirty) -{ - var style = this._style; - - // check if style has changed.. - if(this.localStyleID !== style.styleID) - { - this.dirty = true; - this.localStyleID = style.styleID; - } - - if (!this.dirty && respectDirty) { - return; - } - - // build canvas api font setting from invididual components. Convert a numeric style.fontSize to px - var fontSizeString = (typeof style.fontSize === 'number') ? style.fontSize + 'px' : style.fontSize; - this._font = style.fontStyle + ' ' + style.fontVariant + ' ' + style.fontWeight + ' ' + fontSizeString + ' ' + style.fontFamily; - - this.context.font = this._font; - - // word wrap - // preserve original text - var outputText = style.wordWrap ? this.wordWrap(this._text) : this._text; - - // split text into lines - var lines = outputText.split(/(?:\r\n|\r|\n)/); - - // calculate text width - var lineWidths = new Array(lines.length); - var maxLineWidth = 0; - var fontProperties = this.determineFontProperties(this._font); - - var i; - for (i = 0; i < lines.length; i++) - { - var lineWidth = this.context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing); - lineWidths[i] = lineWidth; - maxLineWidth = Math.max(maxLineWidth, lineWidth); - } - - var width = maxLineWidth + style.strokeThickness; - if (style.dropShadow) - { - width += style.dropShadowDistance; - } - - width += style.padding * 2; - - this.canvas.width = Math.ceil( ( width + this.context.lineWidth ) * this.resolution ); - - // calculate text height - var lineHeight = this.style.lineHeight || fontProperties.fontSize + style.strokeThickness; - - var height = Math.max(lineHeight, fontProperties.fontSize + style.strokeThickness) + (lines.length - 1) * lineHeight; - if (style.dropShadow) - { - height += style.dropShadowDistance; - } - - this.canvas.height = Math.ceil( ( height + this._style.padding * 2 ) * this.resolution ); - - this.context.scale( this.resolution, this.resolution); - - if (navigator.isCocoonJS) - { - this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); - - } - -// this.context.fillStyle="#FF0000"; -// this.context.fillRect(0, 0, this.canvas.width, this.canvas.height); - - this.context.font = this._font; - this.context.strokeStyle = style.stroke; - this.context.lineWidth = style.strokeThickness; - this.context.textBaseline = style.textBaseline; - this.context.lineJoin = style.lineJoin; - this.context.miterLimit = style.miterLimit; - - var linePositionX; - var linePositionY; - - if (style.dropShadow) - { - if (style.dropShadowBlur > 0) { - this.context.shadowColor = style.dropShadowColor; - this.context.shadowBlur = style.dropShadowBlur; - } else { - this.context.fillStyle = style.dropShadowColor; - } - - var xShadowOffset = Math.cos(style.dropShadowAngle) * style.dropShadowDistance; - var yShadowOffset = Math.sin(style.dropShadowAngle) * style.dropShadowDistance; - - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding); - - if (style.stroke && style.strokeThickness) - { - this.context.strokeStyle = style.dropShadowColor; - this.drawLetterSpacing(lines[i], linePositionX + xShadowOffset + style.padding, linePositionY + yShadowOffset + style.padding, true); - this.context.strokeStyle = style.stroke; - } - } - } - } - - //set canvas text styles - this.context.fillStyle = this._generateFillStyle(style, lines); - - //draw lines line by line - for (i = 0; i < lines.length; i++) - { - linePositionX = style.strokeThickness / 2; - linePositionY = (style.strokeThickness / 2 + i * lineHeight) + fontProperties.ascent; - - if (style.align === 'right') - { - linePositionX += maxLineWidth - lineWidths[i]; - } - else if (style.align === 'center') - { - linePositionX += (maxLineWidth - lineWidths[i]) / 2; - } - - if (style.stroke && style.strokeThickness) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding, true); - } - - if (style.fill) - { - this.drawLetterSpacing(lines[i], linePositionX + style.padding, linePositionY + style.padding); - } - } - - this.updateTexture(); -}; - -/** - * Render the text with letter-spacing. - * @param {string} text - The text to draw - * @param {number} x - Horizontal position to draw the text - * @param {number} y - Vertical position to draw the text - * @param {boolean} isStroke - Is this drawing for the outside stroke of the text? If not, it's for the inside fill - * @private - */ -Text.prototype.drawLetterSpacing = function(text, x, y, isStroke) -{ - var style = this._style; - - // letterSpacing of 0 means normal - var letterSpacing = style.letterSpacing; - - if (letterSpacing === 0) - { - if (isStroke) - { - this.context.strokeText(text, x, y); - } - else - { - this.context.fillText(text, x, y); - } - return; - } - - var characters = String.prototype.split.call(text, ''), - index = 0, - current, - currentPosition = x; - - while (index < text.length) - { - current = characters[index++]; - if (isStroke) - { - this.context.strokeText(current, currentPosition, y); - } - else - { - this.context.fillText(current, currentPosition, y); - } - currentPosition += this.context.measureText(current).width + letterSpacing; - } -}; - -/** - * Updates texture size based on canvas size - * - * @private - */ -Text.prototype.updateTexture = function () -{ - var texture = this._texture; - var style = this._style; - - texture.baseTexture.hasLoaded = true; - texture.baseTexture.resolution = this.resolution; - - texture.baseTexture.realWidth = this.canvas.width; - texture.baseTexture.realHeight = this.canvas.height; - texture.baseTexture.width = this.canvas.width / this.resolution; - texture.baseTexture.height = this.canvas.height / this.resolution; - texture.trim.width = texture._frame.width = this.canvas.width / this.resolution; - texture.trim.height = texture._frame.height = this.canvas.height / this.resolution; - - texture.trim.x = -style.padding; - texture.trim.y = -style.padding; - - texture.orig.width = texture._frame.width- style.padding*2; - texture.orig.height = texture._frame.height - style.padding*2; - - //call sprite onTextureUpdate to update scale if _width or _height were set - this._onTextureUpdate(); - - texture.baseTexture.emit('update', texture.baseTexture); - - this.dirty = false; -}; - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - */ -Text.prototype.renderWebGL = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype.renderWebGL.call(this, renderer); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The renderer - * @private - */ -Text.prototype._renderCanvas = function (renderer) -{ - if(this.resolution !== renderer.resolution) - { - this.resolution = renderer.resolution; - this.dirty = true; - } - - this.updateText(true); - - Sprite.prototype._renderCanvas.call(this, renderer); -}; - -/** - * Calculates the ascent, descent and fontSize of a given fontStyle - * - * @param fontStyle {string} String representing the style of the font - * @return {Object} Font properties object - * @private - */ -Text.prototype.determineFontProperties = function (fontStyle) -{ - var properties = Text.fontPropertiesCache[fontStyle]; - - if (!properties) - { - properties = {}; - - var canvas = Text.fontPropertiesCanvas; - var context = Text.fontPropertiesContext; - - context.font = fontStyle; - - var width = Math.ceil(context.measureText('|MÉq').width); - var baseline = Math.ceil(context.measureText('M').width); - var height = 2 * baseline; - - baseline = baseline * 1.4 | 0; - - canvas.width = width; - canvas.height = height; - - context.fillStyle = '#f00'; - context.fillRect(0, 0, width, height); - - context.font = fontStyle; - - context.textBaseline = 'alphabetic'; - context.fillStyle = '#000'; - context.fillText('|MÉq', 0, baseline); - - var imagedata = context.getImageData(0, 0, width, height).data; - var pixels = imagedata.length; - var line = width * 4; - - var i, j; - - var idx = 0; - var stop = false; - - // ascent. scan from top to bottom until we find a non red pixel - for (i = 0; i < baseline; i++) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx += line; - } - else - { - break; - } - } - - properties.ascent = baseline - i; - - idx = pixels - line; - stop = false; - - // descent. scan from bottom to top until we find a non red pixel - for (i = height; i > baseline; i--) - { - for (j = 0; j < line; j += 4) - { - if (imagedata[idx + j] !== 255) - { - stop = true; - break; - } - } - if (!stop) - { - idx -= line; - } - else - { - break; - } - } - - properties.descent = i - baseline; - properties.fontSize = properties.ascent + properties.descent; - - Text.fontPropertiesCache[fontStyle] = properties; - } - - return properties; -}; - -/** - * Applies newlines to a string to have it optimally fit into the horizontal - * bounds set by the Text object's wordWrapWidth property. - * - * @param text {string} String to apply word wrapping to - * @return {string} New string with new lines applied where required - * @private - */ -Text.prototype.wordWrap = function (text) -{ - // Greedy wrapping algorithm that will wrap words as the line grows longer - // than its horizontal bounds. - var result = ''; - var lines = text.split('\n'); - var wordWrapWidth = this._style.wordWrapWidth; - for (var i = 0; i < lines.length; i++) - { - var spaceLeft = wordWrapWidth; - var words = lines[i].split(' '); - for (var j = 0; j < words.length; j++) - { - var wordWidth = this.context.measureText(words[j]).width; - if (this._style.breakWords && wordWidth > wordWrapWidth) - { - // Word should be split in the middle - var characters = words[j].split(''); - for (var c = 0; c < characters.length; c++) - { - var characterWidth = this.context.measureText(characters[c]).width; - if (characterWidth > spaceLeft) - { - result += '\n' + characters[c]; - spaceLeft = wordWrapWidth - characterWidth; - } - else - { - if (c === 0) - { - result += ' '; - } - result += characters[c]; - spaceLeft -= characterWidth; - } - } - } - else - { - var wordWidthWithSpace = wordWidth + this.context.measureText(' ').width; - if (j === 0 || wordWidthWithSpace > spaceLeft) - { - // Skip printing the newline if it's the first word of the line that is - // greater than the word wrap width. - if (j > 0) - { - result += '\n'; - } - result += words[j]; - spaceLeft = wordWrapWidth - wordWidth; - } - else - { - spaceLeft -= wordWidthWithSpace; - result += ' ' + words[j]; - } - } - } - - if (i < lines.length-1) - { - result += '\n'; - } - } - return result; -}; - -/** - * calculates the bounds of the Text as a rectangle. The bounds calculation takes the worldTransform into account. - */ -Text.prototype._calculateBounds = function () -{ - this.updateText(true); - this.calculateVertices(); - // if we have already done this on THIS frame. - this._bounds.addQuad(this.vertexData); -}; - -/** - * Method to be called upon a TextStyle change. - * @private - */ -Text.prototype._onStyleChange = function () -{ - this.dirty = true; -}; - -/** - * Generates the fill style. Can automatically generate a gradient based on the fill style being an array - * @return string|Number|CanvasGradient - * @private - */ -Text.prototype._generateFillStyle = function (style, lines) -{ - if (!Array.isArray(style.fill)) - { - return style.fill; - } - else - { - // the gradient will be evenly spaced out according to how large the array is. - // ['#FF0000', '#00FF00', '#0000FF'] would created stops at 0.25, 0.5 and 0.75 - var i; - var gradient; - var totalIterations; - var currentIteration; - var stop; - - var width = this.canvas.width / this.resolution; - var height = this.canvas.height / this.resolution; - - if (style.fillGradientType === CONST.TEXT_GRADIENT.LINEAR_VERTICAL) - { - // start the gradient at the top center of the canvas, and end at the bottom middle of the canvas - gradient = this.context.createLinearGradient(width / 2, 0, width / 2, height); - - // we need to repeat the gradient so that each invididual line of text has the same vertical gradient effect - // ['#FF0000', '#00FF00', '#0000FF'] over 2 lines would create stops at 0.125, 0.25, 0.375, 0.625, 0.75, 0.875 - totalIterations = ( style.fill.length + 1 ) * lines.length; - currentIteration = 0; - for (i = 0; i < lines.length; i++) - { - currentIteration += 1; - for (var j = 0; j < style.fill.length; j++) - { - stop = (currentIteration / totalIterations); - gradient.addColorStop(stop, style.fill[j]); - currentIteration++; - } - } - } - else - { - // start the gradient at the center left of the canvas, and end at the center right of the canvas - gradient = this.context.createLinearGradient(0, height / 2, width, height / 2); - - // can just evenly space out the gradients in this case, as multiple lines makes no difference to an even left to right gradient - totalIterations = style.fill.length + 1; - currentIteration = 1; - - for (i = 0; i < style.fill.length; i++) - { - stop = currentIteration / totalIterations; - gradient.addColorStop(stop, style.fill[i]); - currentIteration++; - } - } - - return gradient; - } -}; - -/** - * Destroys this text object. - * Note* Unlike a Sprite, a Text object will automatically destroy its baseTexture and texture as - * the majorety of the time the texture will not be shared with any other Sprites. - * - * @param [options] {object|boolean} Options parameter. A boolean will act as if all options have been set to that value - * @param [options.children=false] {boolean} if set to true, all the children will have their destroy - * method called as well. 'options' will be passed on to those calls. - * @param [options.texture=true] {boolean} Should it destroy the current texture of the sprite as well - * @param [options.baseTexture=true] {boolean} Should it destroy the base texture of the sprite as well - */ -Text.prototype.destroy = function (options) -{ - if (typeof options === 'boolean') { - options = { children: options }; - } - - options = Object.assign({}, defaultDestroyOptions, options); - - Sprite.prototype.destroy.call(this, options); - - // make sure to reset the the context and canvas.. dont want this hanging around in memory! - this.context = null; - this.canvas = null; - - this._style = null; -}; diff --git a/src/core/text/TextStyle.js b/src/core/text/TextStyle.js index 367779a..4506e7e 100644 --- a/src/core/text/TextStyle.js +++ b/src/core/text/TextStyle.js @@ -37,13 +37,39 @@ * @param [style.wordWrap=false] {boolean} Indicates if word wrap should be used * @param [style.wordWrapWidth=100] {number} The width at which text will wrap, it needs wordWrap to be set to true */ -function TextStyle(style) -{ - this.styleID = 0; - Object.assign(this, this._defaults, style); +class TextStyle { + constructor(style) + { + this.styleID = 0; + Object.assign(this, this._defaults, style); + } + + /** + * Creates a new TextStyle object with the same values as this one. + * Note that the only the properties of the object are cloned. + * + * @return {PIXI.TextStyle} New cloned TextStyle object + */ + clone() + { + var clonedProperties = {}; + for (var key in this._defaults) + { + clonedProperties[key] = this[key]; + } + return new TextStyle(clonedProperties); + } + + /** + * Resets all properties to the defaults specified in TextStyle.prototype._default + */ + reset() + { + Object.assign(this, this._defaults); + } + } -TextStyle.prototype.constructor = TextStyle; module.exports = TextStyle; // Default settings. Explained in the constructor. @@ -75,30 +101,6 @@ }; /** - * Creates a new TextStyle object with the same values as this one. - * Note that the only the properties of the object are cloned. - * - * @return {PIXI.TextStyle} New cloned TextStyle object - */ -TextStyle.prototype.clone = function () -{ - var clonedProperties = {}; - for (var key in this._defaults) - { - clonedProperties[key] = this[key]; - } - return new TextStyle(clonedProperties); -}; - -/** - * Resets all properties to the defaults specified in TextStyle.prototype._default - */ -TextStyle.prototype.reset = function () -{ - Object.assign(this, this._defaults); -}; - -/** * Create setters and getters for each of the style properties. Converts colors where necessary. */ Object.defineProperties(TextStyle.prototype, { diff --git a/src/core/textures/BaseRenderTexture.js b/src/core/textures/BaseRenderTexture.js index 721dc16..972c26e 100644 --- a/src/core/textures/BaseRenderTexture.js +++ b/src/core/textures/BaseRenderTexture.js @@ -45,87 +45,87 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ -function BaseRenderTexture(width, height, scaleMode, resolution) -{ - BaseTexture.call(this, null, scaleMode); +class BaseRenderTexture extends BaseTexture { + constructor(width, height, scaleMode, resolution) + { + super(null, scaleMode); - this.resolution = resolution || CONST.RESOLUTION; + this.resolution = resolution || CONST.RESOLUTION; - this.width = width || 100; - this.height = height || 100; + this.width = width || 100; + this.height = height || 100; - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - this.hasLoaded = true; + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; + this.hasLoaded = true; + + /** + * A map of renderer IDs to webgl renderTargets + * + * @member {object} + * @private + */ + this._glRenderTargets = []; + + /** + * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * + * @member {object} + * @private + */ + this._canvasRenderTarget = null; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + } /** - * A map of renderer IDs to webgl renderTargets + * Resizes the BaseRenderTexture. * - * @member {object} - * @private + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. */ - this._glRenderTargets = []; + resize(width, height) + { + + if (width === this.width && height === this.height) + { + return; + } + + this.valid = (width > 0 && height > 0); + + this.width = width; + this.height = height; + + this.realWidth = this.width * this.resolution; + this.realHeight = this.height * this.resolution; + + if (!this.valid) + { + return; + } + + this.emit('update', this); + + } /** - * A reference to the canvas render target (we only need one as this can be shared accross renderers) + * Destroys this texture * - * @member {object} - * @private */ - this._canvasRenderTarget = null; + destroy() + { + super.destroy(true); + this.renderer = null; + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; } -BaseRenderTexture.prototype = Object.create(BaseTexture.prototype); -BaseRenderTexture.prototype.constructor = BaseRenderTexture; module.exports = BaseRenderTexture; - -/** - * Resizes the BaseRenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - */ -BaseRenderTexture.prototype.resize = function (width, height) -{ - - if (width === this.width && height === this.height) - { - return; - } - - this.valid = (width > 0 && height > 0); - - this.width = width; - this.height = height; - - this.realWidth = this.width * this.resolution; - this.realHeight = this.height * this.resolution; - - if (!this.valid) - { - return; - } - - this.emit('update', this); - -}; - -/** - * Destroys this texture - * - */ -BaseRenderTexture.prototype.destroy = function () -{ - BaseTexture.prototype.destroy.call(this, true); - this.renderer = null; -}; - diff --git a/src/core/textures/BaseTexture.js b/src/core/textures/BaseTexture.js index 644acc8..d3df3a2 100644 --- a/src/core/textures/BaseTexture.js +++ b/src/core/textures/BaseTexture.js @@ -13,436 +13,437 @@ * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values * @param [resolution=1] {number} The resolution / device pixel ratio of the texture */ -function BaseTexture(source, scaleMode, resolution) -{ - EventEmitter.call(this); - - this.uid = utils.uid(); - - this.touched = 0; - - /** - * The resolution / device pixel ratio of the texture - * - * @member {number} - * @default 1 - */ - this.resolution = resolution || CONST.RESOLUTION; - - /** - * The width of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.width = 100; - - /** - * The height of the base texture set when the image has loaded - * - * @member {number} - * @readonly - */ - this.height = 100; - - // TODO docs - // used to store the actual dimensions of the source - /** - * Used to store the actual width of the source of this texture - * - * @member {number} - * @readonly - */ - this.realWidth = 100; - /** - * Used to store the actual height of the source of this texture - * - * @member {number} - * @readonly - */ - this.realHeight = 100; - - /** - * The scale mode to apply when scaling this texture - * - * @member {number} - * @default PIXI.SCALE_MODES.DEFAULT - * @see PIXI.SCALE_MODES - */ - this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; - - /** - * Set to true once the base texture has successfully loaded. - * - * This is never true if the underlying source fails to load or has no texture data. - * - * @member {boolean} - * @readonly - */ - this.hasLoaded = false; - - /** - * Set to true if the source is currently loading. - * - * If an Image source is loading the 'loaded' or 'error' event will be - * dispatched when the operation ends. An underyling source that is - * immediately-available bypasses loading entirely. - * - * @member {boolean} - * @readonly - */ - this.isLoading = false; - - /** - * The image source that is used to create the texture. - * - * TODO: Make this a setter that calls loadSource(); - * - * @member {HTMLImageElement|HTMLCanvasElement} - * @readonly - */ - this.source = null; // set in loadSource, if at all - - /** - * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) - * All blend modes, and shaders written for default value. Change it on your own risk. - * - * @member {boolean} - * @default true - */ - this.premultipliedAlpha = true; - - /** - * The image url of the texture - * - * @member {string} - */ - this.imageUrl = null; - - /** - * Wether or not the texture is a power of two, try to use power of two textures as much as you can - * @member {boolean} - * @private - */ - this.isPowerOfTwo = false; - - // used for webGL - - /** - * - * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used - * Also the texture must be a power of two size to work - * - * @member {boolean} - * @see PIXI.MIPMAP_TEXTURES - */ - this.mipmap = CONST.MIPMAP_TEXTURES; - - /** - * - * WebGL Texture wrap mode - * - * @member {number} - * @see PIXI.WRAP_MODES - */ - this.wrapMode = CONST.WRAP_MODES.DEFAULT; - - /** - * A map of renderer IDs to webgl textures - * - * @member {object} - * @private - */ - this._glTextures = []; - this._enabled = 0; - this._id = 0; - - // if no source passed don't try to load - if (source) +class BaseTexture extends EventEmitter { + constructor(source, scaleMode, resolution) { - this.loadSource(source); - } + super(); - /** - * Fired when a not-immediately-available source finishes loading. - * - * @event loaded - * @memberof PIXI.BaseTexture# - * @protected - */ + this.uid = utils.uid(); - /** - * Fired when a not-immediately-available source fails to load. - * - * @event error - * @memberof PIXI.BaseTexture# - * @protected - */ -} + this.touched = 0; -BaseTexture.prototype = Object.create(EventEmitter.prototype); -BaseTexture.prototype.constructor = BaseTexture; -module.exports = BaseTexture; + /** + * The resolution / device pixel ratio of the texture + * + * @member {number} + * @default 1 + */ + this.resolution = resolution || CONST.RESOLUTION; -/** - * Updates the texture on all the webgl renderers, this also assumes the src has changed. - * - * @fires update - */ -BaseTexture.prototype.update = function () -{ - this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; - this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + /** + * The width of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.width = 100; - this.width = this.realWidth / this.resolution; - this.height = this.realHeight / this.resolution; + /** + * The height of the base texture set when the image has loaded + * + * @member {number} + * @readonly + */ + this.height = 100; - this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + // TODO docs + // used to store the actual dimensions of the source + /** + * Used to store the actual width of the source of this texture + * + * @member {number} + * @readonly + */ + this.realWidth = 100; + /** + * Used to store the actual height of the source of this texture + * + * @member {number} + * @readonly + */ + this.realHeight = 100; - this.emit('update', this); -}; + /** + * The scale mode to apply when scaling this texture + * + * @member {number} + * @default PIXI.SCALE_MODES.DEFAULT + * @see PIXI.SCALE_MODES + */ + this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT; -/** - * Load a source. - * - * If the source is not-immediately-available, such as an image that needs to be - * downloaded, then the 'loaded' or 'error' event will be dispatched in the future - * and `hasLoaded` will remain false after this call. - * - * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: - * - * if (texture.hasLoaded) { - * // texture ready for use - * } else if (texture.isLoading) { - * // listen to 'loaded' and/or 'error' events on texture - * } else { - * // not loading, not going to load UNLESS the source is reloaded - * // (it may still make sense to listen to the events) - * } - * - * @protected - * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. - */ -BaseTexture.prototype.loadSource = function (source) -{ - var wasLoading = this.isLoading; - this.hasLoaded = false; - this.isLoading = false; + /** + * Set to true once the base texture has successfully loaded. + * + * This is never true if the underlying source fails to load or has no texture data. + * + * @member {boolean} + * @readonly + */ + this.hasLoaded = false; - if (wasLoading && this.source) - { - this.source.onload = null; - this.source.onerror = null; - } + /** + * Set to true if the source is currently loading. + * + * If an Image source is loading the 'loaded' or 'error' event will be + * dispatched when the operation ends. An underyling source that is + * immediately-available bypasses loading entirely. + * + * @member {boolean} + * @readonly + */ + this.isLoading = false; - this.source = source; + /** + * The image source that is used to create the texture. + * + * TODO: Make this a setter that calls loadSource(); + * + * @member {HTMLImageElement|HTMLCanvasElement} + * @readonly + */ + this.source = null; // set in loadSource, if at all - // Apply source if loaded. Otherwise setup appropriate loading monitors. - if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) - { - this._sourceLoaded(); - } - else if (!source.getContext) - { + /** + * Controls if RGB channels should be pre-multiplied by Alpha (WebGL only) + * All blend modes, and shaders written for default value. Change it on your own risk. + * + * @member {boolean} + * @default true + */ + this.premultipliedAlpha = true; - // Image fail / not ready - this.isLoading = true; - - var scope = this; - - source.onload = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope._sourceLoaded(); - - scope.emit('loaded', scope); - }; - - source.onerror = function () - { - source.onload = null; - source.onerror = null; - - if (!scope.isLoading) - { - return; - } - - scope.isLoading = false; - scope.emit('error', scope); - }; - - // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element - // "The value of `complete` can thus change while a script is executing." - // So complete needs to be re-checked after the callbacks have been added.. - // NOTE: complete will be true if the image has no src so best to check if the src is set. - if (source.complete && source.src) - { - this.isLoading = false; - - // ..and if we're complete now, no need for callbacks - source.onload = null; - source.onerror = null; - - if (source.width && source.height) - { - this._sourceLoaded(); - - // If any previous subscribers possible - if (wasLoading) - { - this.emit('loaded', this); - } - } - else - { - // If any previous subscribers possible - if (wasLoading) - { - this.emit('error', this); - } - } - } - } -}; - -/** - * Used internally to update the width, height, and some other tracking vars once - * a source has successfully loaded. - * - * @private - */ -BaseTexture.prototype._sourceLoaded = function () -{ - this.hasLoaded = true; - this.update(); -}; - -/** - * Destroys this base texture - * - */ -BaseTexture.prototype.destroy = function () -{ - if (this.imageUrl) - { - delete utils.BaseTextureCache[this.imageUrl]; - delete utils.TextureCache[this.imageUrl]; - + /** + * The image url of the texture + * + * @member {string} + */ this.imageUrl = null; - if (!navigator.isCocoonJS) + /** + * Wether or not the texture is a power of two, try to use power of two textures as much as you can + * @member {boolean} + * @private + */ + this.isPowerOfTwo = false; + + // used for webGL + + /** + * + * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used + * Also the texture must be a power of two size to work + * + * @member {boolean} + * @see PIXI.MIPMAP_TEXTURES + */ + this.mipmap = CONST.MIPMAP_TEXTURES; + + /** + * + * WebGL Texture wrap mode + * + * @member {number} + * @see PIXI.WRAP_MODES + */ + this.wrapMode = CONST.WRAP_MODES.DEFAULT; + + /** + * A map of renderer IDs to webgl textures + * + * @member {object} + * @private + */ + this._glTextures = []; + this._enabled = 0; + this._id = 0; + + // if no source passed don't try to load + if (source) { - this.source.src = ''; - } - } - else if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[this.source._pixiId]; - } - - this.source = null; - - this.dispose(); -}; - -/** - * Frees the texture from WebGL memory without destroying this texture object. - * This means you can still use the texture later which will upload it to GPU - * memory again. - * - */ -BaseTexture.prototype.dispose = function () -{ - this.emit('dispose', this); - - // this should no longer be needed, the renderers should cleanup all the gl textures. - // this._glTextures = {}; -}; - -/** - * Changes the source image of the texture. - * The original source must be an Image element. - * - * @param newSrc {string} the path of the image - */ -BaseTexture.prototype.updateSourceImage = function (newSrc) -{ - this.source.src = newSrc; - - this.loadSource(this.source); -}; - -/** - * Helper function that creates a base texture from the given image url. - * If the image is not in the base texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var baseTexture = utils.BaseTextureCache[imageUrl]; - - if (!baseTexture) - { - // new Image() breaks tex loading in some versions of Chrome. - // See https://code.google.com/p/chromium/issues/detail?id=238071 - var image = new Image();//document.createElement('img'); - - - if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) - { - image.crossOrigin = determineCrossOrigin(imageUrl); + this.loadSource(source); } - baseTexture = new BaseTexture(image, scaleMode); - baseTexture.imageUrl = imageUrl; + /** + * Fired when a not-immediately-available source finishes loading. + * + * @event loaded + * @memberof PIXI.BaseTexture# + * @protected + */ - image.src = imageUrl; - - utils.BaseTextureCache[imageUrl] = baseTexture; - - // if there is an @2x at the end of the url we are going to assume its a highres image - baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + /** + * Fired when a not-immediately-available source fails to load. + * + * @event error + * @memberof PIXI.BaseTexture# + * @protected + */ } - return baseTexture; -}; - -/** - * Helper function that creates a base texture from the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return PIXI.BaseTexture - */ -BaseTexture.fromCanvas = function (canvas, scaleMode) -{ - if (!canvas._pixiId) + /** + * Updates the texture on all the webgl renderers, this also assumes the src has changed. + * + * @fires update + */ + update() { - canvas._pixiId = 'canvas_' + utils.uid(); + this.realWidth = this.source.naturalWidth || this.source.videoWidth || this.source.width; + this.realHeight = this.source.naturalHeight || this.source.videoHeight || this.source.height; + + this.width = this.realWidth / this.resolution; + this.height = this.realHeight / this.resolution; + + this.isPowerOfTwo = bitTwiddle.isPow2(this.realWidth) && bitTwiddle.isPow2(this.realHeight); + + this.emit('update', this); } - var baseTexture = utils.BaseTextureCache[canvas._pixiId]; - - if (!baseTexture) + /** + * Load a source. + * + * If the source is not-immediately-available, such as an image that needs to be + * downloaded, then the 'loaded' or 'error' event will be dispatched in the future + * and `hasLoaded` will remain false after this call. + * + * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is: + * + * if (texture.hasLoaded) { + * // texture ready for use + * } else if (texture.isLoading) { + * // listen to 'loaded' and/or 'error' events on texture + * } else { + * // not loading, not going to load UNLESS the source is reloaded + * // (it may still make sense to listen to the events) + * } + * + * @protected + * @param source {HTMLImageElement|HTMLCanvasElement} the source object of the texture. + */ + loadSource(source) { - baseTexture = new BaseTexture(canvas, scaleMode); - utils.BaseTextureCache[canvas._pixiId] = baseTexture; + var wasLoading = this.isLoading; + this.hasLoaded = false; + this.isLoading = false; + + if (wasLoading && this.source) + { + this.source.onload = null; + this.source.onerror = null; + } + + this.source = source; + + // Apply source if loaded. Otherwise setup appropriate loading monitors. + if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) + { + this._sourceLoaded(); + } + else if (!source.getContext) + { + + // Image fail / not ready + this.isLoading = true; + + var scope = this; + + source.onload = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope._sourceLoaded(); + + scope.emit('loaded', scope); + }; + + source.onerror = function () + { + source.onload = null; + source.onerror = null; + + if (!scope.isLoading) + { + return; + } + + scope.isLoading = false; + scope.emit('error', scope); + }; + + // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element + // "The value of `complete` can thus change while a script is executing." + // So complete needs to be re-checked after the callbacks have been added.. + // NOTE: complete will be true if the image has no src so best to check if the src is set. + if (source.complete && source.src) + { + this.isLoading = false; + + // ..and if we're complete now, no need for callbacks + source.onload = null; + source.onerror = null; + + if (source.width && source.height) + { + this._sourceLoaded(); + + // If any previous subscribers possible + if (wasLoading) + { + this.emit('loaded', this); + } + } + else + { + // If any previous subscribers possible + if (wasLoading) + { + this.emit('error', this); + } + } + } + } } - return baseTexture; -}; + /** + * Used internally to update the width, height, and some other tracking vars once + * a source has successfully loaded. + * + * @private + */ + _sourceLoaded() + { + this.hasLoaded = true; + this.update(); + } + + /** + * Destroys this base texture + * + */ + destroy() + { + if (this.imageUrl) + { + delete utils.BaseTextureCache[this.imageUrl]; + delete utils.TextureCache[this.imageUrl]; + + this.imageUrl = null; + + if (!navigator.isCocoonJS) + { + this.source.src = ''; + } + } + else if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[this.source._pixiId]; + } + + this.source = null; + + this.dispose(); + } + + /** + * Frees the texture from WebGL memory without destroying this texture object. + * This means you can still use the texture later which will upload it to GPU + * memory again. + * + */ + dispose() + { + this.emit('dispose', this); + + // this should no longer be needed, the renderers should cleanup all the gl textures. + // this._glTextures = {}; + } + + /** + * Changes the source image of the texture. + * The original source must be an Image element. + * + * @param newSrc {string} the path of the image + */ + updateSourceImage(newSrc) + { + this.source.src = newSrc; + + this.loadSource(this.source); + } + + /** + * Helper function that creates a base texture from the given image url. + * If the image is not in the base texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI. + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var baseTexture = utils.BaseTextureCache[imageUrl]; + + if (!baseTexture) + { + // new Image() breaks tex loading in some versions of Chrome. + // See https://code.google.com/p/chromium/issues/detail?id=238071 + var image = new Image();//document.createElement('img'); + + + if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) + { + image.crossOrigin = determineCrossOrigin(imageUrl); + } + + baseTexture = new BaseTexture(image, scaleMode); + baseTexture.imageUrl = imageUrl; + + image.src = imageUrl; + + utils.BaseTextureCache[imageUrl] = baseTexture; + + // if there is an @2x at the end of the url we are going to assume its a highres image + baseTexture.resolution = utils.getResolutionOfUrl(imageUrl); + } + + return baseTexture; + } + + /** + * Helper function that creates a base texture from the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return PIXI.BaseTexture + */ + static fromCanvas(canvas, scaleMode) + { + if (!canvas._pixiId) + { + canvas._pixiId = 'canvas_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[canvas._pixiId]; + + if (!baseTexture) + { + baseTexture = new BaseTexture(canvas, scaleMode); + utils.BaseTextureCache[canvas._pixiId] = baseTexture; + } + + return baseTexture; + } + +} + +module.exports = BaseTexture; diff --git a/src/core/textures/RenderTexture.js b/src/core/textures/RenderTexture.js index 3dea237..7930b21 100644 --- a/src/core/textures/RenderTexture.js +++ b/src/core/textures/RenderTexture.js @@ -40,83 +40,86 @@ * @param baseRenderTexture {PIXI.BaseRenderTexture} The renderer used for this RenderTexture * @param [frame] {PIXI.Rectangle} The rectangle frame of the texture to show */ -function RenderTexture(baseRenderTexture, frame) -{ - // suport for legacy.. - this.legacyRenderer = null; - - if( !(baseRenderTexture instanceof BaseRenderTexture) ) +class RenderTexture extends Texture { + constructor(baseRenderTexture, frame) { - var width = arguments[1]; - var height = arguments[2]; - var scaleMode = arguments[3] || 0; - var resolution = arguments[4] || 1; + // suport for legacy.. + let _legacyRenderer = null; - // we have an old render texture.. - console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line - this.legacyRenderer = arguments[0]; + if( !(baseRenderTexture instanceof BaseRenderTexture) ) + { + var width = arguments[1]; + var height = arguments[2]; + var scaleMode = arguments[3] || 0; + var resolution = arguments[4] || 1; - frame = null; - baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + // we have an old render texture.. + console.warn('v4 RenderTexture now expects a new BaseRenderTexture. Please use RenderTexture.create('+width+', '+height+')'); // jshint ignore:line + _legacyRenderer = arguments[0]; + + frame = null; + baseRenderTexture = new BaseRenderTexture(width, height, scaleMode, resolution); + } + + + /** + * The base texture object that this texture uses + * + * @member {BaseTexture} + */ + super( + baseRenderTexture, + frame + ); + + this.legacyRenderer = _legacyRenderer; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = true; + + this._updateUvs(); } + /** + * Resizes the RenderTexture. + * + * @param width {number} The width to resize to. + * @param height {number} The height to resize to. + * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? + */ + resize(width, height, doNotResizeBaseTexture) + { + + //TODO - could be not required.. + this.valid = (width > 0 && height > 0); + + this._frame.width = this.orig.width = width; + this._frame.height = this.orig.height = height; + + if (!doNotResizeBaseTexture) + { + this.baseTexture.resize(width, height); + } + + this._updateUvs(); + } /** - * The base texture object that this texture uses - * - * @member {BaseTexture} + * A short hand way of creating a render texture.. + * @param [width=100] {number} The width of the render texture + * @param [height=100] {number} The height of the render texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated */ - Texture.call(this, - baseRenderTexture, - frame - ); + static create(width, height, scaleMode, resolution) + { + return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); + } - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = true; - - this._updateUvs(); } -RenderTexture.prototype = Object.create(Texture.prototype); -RenderTexture.prototype.constructor = RenderTexture; module.exports = RenderTexture; - -/** - * Resizes the RenderTexture. - * - * @param width {number} The width to resize to. - * @param height {number} The height to resize to. - * @param doNotResizeBaseTexture {boolean} Should the baseTexture.width and height values be resized as well? - */ -RenderTexture.prototype.resize = function (width, height, doNotResizeBaseTexture) -{ - - //TODO - could be not required.. - this.valid = (width > 0 && height > 0); - - this._frame.width = this.orig.width = width; - this._frame.height = this.orig.height = height; - - if (!doNotResizeBaseTexture) - { - this.baseTexture.resize(width, height); - } - - this._updateUvs(); -}; - -/** - * A short hand way of creating a render texture.. - * @param [width=100] {number} The width of the render texture - * @param [height=100] {number} The height of the render texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @param [resolution=1] {number} The resolution / device pixel ratio of the texture being generated - */ -RenderTexture.create = function(width, height, scaleMode, resolution) -{ - return new RenderTexture(new BaseRenderTexture(width, height, scaleMode, resolution)); -}; diff --git a/src/core/textures/Texture.js b/src/core/textures/Texture.js index 5f91b18..ebbfa17 100644 --- a/src/core/textures/Texture.js +++ b/src/core/textures/Texture.js @@ -25,120 +25,403 @@ * @param [trim] {PIXI.Rectangle} Trimmed rectangle of original texture * @param [rotate] {number} indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8} */ -function Texture(baseTexture, frame, orig, trim, rotate) -{ - EventEmitter.call(this); - - /** - * Does this Texture have any frame data assigned to it? - * - * @member {boolean} - */ - this.noFrame = false; - - if (!frame) +class Texture extends EventEmitter { + constructor(baseTexture, frame, orig, trim, rotate) { - this.noFrame = true; - frame = new math.Rectangle(0, 0, 1, 1); - } + super(); - if (baseTexture instanceof Texture) - { - baseTexture = baseTexture.baseTexture; + /** + * Does this Texture have any frame data assigned to it? + * + * @member {boolean} + */ + this.noFrame = false; + + if (!frame) + { + this.noFrame = true; + frame = new math.Rectangle(0, 0, 1, 1); + } + + if (baseTexture instanceof Texture) + { + baseTexture = baseTexture.baseTexture; + } + + /** + * The base texture that this texture uses. + * + * @member {PIXI.BaseTexture} + */ + this.baseTexture = baseTexture; + + /** + * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, + * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * + * @member {PIXI.Rectangle} + */ + this._frame = frame; + + /** + * This is the trimmed area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.trim = trim; + + /** + * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. + * + * @member {boolean} + */ + this.valid = false; + + /** + * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) + * + * @member {boolean} + */ + this.requiresUpdate = false; + + /** + * The WebGL UV data cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = null; + + /** + * This is the area of original texture, before it was put in atlas + * + * @member {PIXI.Rectangle} + */ + this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); + + this._rotate = +(rotate || 0); + + if (rotate === true) { + // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures + this._rotate = 2; + } else { + if (this._rotate % 2 !== 0) { + throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; + } + } + + if (baseTexture.hasLoaded) + { + if (this.noFrame) + { + frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); + + // if there is no frame we should monitor for any base texture changes.. + baseTexture.on('update', this.onBaseTextureUpdated, this); + } + this.frame = frame; + } + else + { + baseTexture.once('loaded', this.onBaseTextureLoaded, this); + } + + /** + * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * + * @event update + * @memberof PIXI.Texture# + * @protected + */ + + + this._updateID = 0; } /** - * The base texture that this texture uses. + * Updates this texture on the gpu. * - * @member {PIXI.BaseTexture} */ - this.baseTexture = baseTexture; + update() + { + this.baseTexture.update(); + } /** - * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering, - * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases) + * Called when the base texture is loaded * - * @member {PIXI.Rectangle} - */ - this._frame = frame; - - /** - * This is the trimmed area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.trim = trim; - - /** - * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered. - * - * @member {boolean} - */ - this.valid = false; - - /** - * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates) - * - * @member {boolean} - */ - this.requiresUpdate = false; - - /** - * The WebGL UV data cache. - * - * @member {PIXI.TextureUvs} * @private */ - this._uvs = null; - - /** - * This is the area of original texture, before it was put in atlas - * - * @member {PIXI.Rectangle} - */ - this.orig = orig || frame;//new math.Rectangle(0, 0, 1, 1); - - this._rotate = +(rotate || 0); - - if (rotate === true) { - // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures - this._rotate = 2; - } else { - if (this._rotate % 2 !== 0) { - throw 'attempt to use diamond-shaped UVs. If you are sure, set rotation manually'; - } - } - - if (baseTexture.hasLoaded) + onBaseTextureLoaded(baseTexture) { + this._updateID++; + + // TODO this code looks confusing.. boo to abusing getters and setterss! if (this.noFrame) { - frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - - // if there is no frame we should monitor for any base texture changes.. - baseTexture.on('update', this.onBaseTextureUpdated, this); + this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); } - this.frame = frame; - } - else - { - baseTexture.once('loaded', this.onBaseTextureLoaded, this); + else + { + this.frame = this._frame; + } + + this.baseTexture.on('update', this.onBaseTextureUpdated, this); + this.emit('update', this); + } /** - * Fired when the texture is updated. This happens if the frame or the baseTexture is updated. + * Called when the base texture is updated * - * @event update - * @memberof PIXI.Texture# + * @private + */ + onBaseTextureUpdated(baseTexture) + { + this._updateID++; + + this._frame.width = baseTexture.width; + this._frame.height = baseTexture.height; + + this.emit('update', this); + } + + /** + * Destroys this texture + * + * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well + */ + destroy(destroyBase) + { + if (this.baseTexture) + { + + if (destroyBase) + { + // delete the texture if it exists in the texture cache.. + // this only needs to be removed if the base texture is actually destoryed too.. + if(utils.TextureCache[this.baseTexture.imageUrl]) + { + delete utils.TextureCache[this.baseTexture.imageUrl]; + } + + this.baseTexture.destroy(); + } + + this.baseTexture.off('update', this.onBaseTextureUpdated, this); + this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); + + this.baseTexture = null; + } + + this._frame = null; + this._uvs = null; + this.trim = null; + this.orig = null; + + this.valid = false; + + this.off('dispose', this.dispose, this); + this.off('update', this.update, this); + } + + /** + * Creates a new texture object that acts the same as this one. + * + * @return {PIXI.Texture} + */ + clone() + { + return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); + } + + /** + * Updates the internal WebGL UV cache. + * * @protected */ + _updateUvs() + { + if (!this._uvs) + { + this._uvs = new TextureUvs(); + } + + this._uvs.set(this._frame, this.baseTexture, this.rotate); + + this._updateID++; + } + + /** + * Helper function that creates a Texture object from the given image url. + * If the image is not in the texture cache it will be created and loaded. + * + * @static + * @param imageUrl {string} The image url of the texture + * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromImage(imageUrl, crossorigin, scaleMode) + { + var texture = utils.TextureCache[imageUrl]; + + if (!texture) + { + texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); + utils.TextureCache[imageUrl] = texture; + } + + return texture; + } + + /** + * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.Texture} The newly created texture + */ + static fromFrame(frameId) + { + var texture = utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); + } + + return texture; + } + + /** + * Helper function that creates a new Texture based on the given canvas element. + * + * @static + * @param canvas {HTMLCanvasElement} The canvas element source of the texture + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromCanvas(canvas, scaleMode) + { + return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the given video element. + * + * @static + * @param video {HTMLVideoElement|string} The URL or actual element of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideo(video, scaleMode) + { + if (typeof video === 'string') + { + return Texture.fromVideoUrl(video, scaleMode); + } + else + { + return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); + } + } + + /** + * Helper function that creates a new Texture based on the video url. + * + * @static + * @param videoUrl {string} URL of the video + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.Texture} The newly created texture + */ + static fromVideoUrl(videoUrl, scaleMode) + { + return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); + } + + /** + * Helper function that creates a new Texture based on the source you provide. + * The soucre can be - frame id, image url, video url, canvae element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @return {PIXI.Texture} The newly created texture + */ + static from(source) + { + //TODO auto detect cross origin.. + //TODO pass in scale mode? + if(typeof source === 'string') + { + var texture = utils.TextureCache[source]; + + if (!texture) + { + // check if its a video.. + var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; + if(isVideo) + { + return Texture.fromVideoUrl(source); + } + + return Texture.fromImage(source); + } + + return texture; + } + else if(source instanceof HTMLCanvasElement) + { + return Texture.fromCanvas(source); + } + else if(source instanceof HTMLVideoElement) + { + return Texture.fromVideo(source); + } + else if(source instanceof BaseTexture) + { + return new Texture(BaseTexture); + } + else + { + // lets assume its a texture! + return source; + } + } - this._updateID = 0; + /** + * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. + * + * @static + * @param texture {PIXI.Texture} The Texture to add to the cache. + * @param id {string} The id that the texture will be stored against. + */ + static addTextureToCache(texture, id) + { + utils.TextureCache[id] = texture; + } + + /** + * Remove a texture from the global utils.TextureCache. + * + * @static + * @param id {string} The id of the texture to be removed + * @return {PIXI.Texture} The texture that was removed + */ + static removeTextureFromCache(id) + { + var texture = utils.TextureCache[id]; + + delete utils.TextureCache[id]; + delete utils.BaseTextureCache[id]; + + return texture; + } + } -Texture.prototype = Object.create(EventEmitter.prototype); -Texture.prototype.constructor = Texture; module.exports = Texture; Object.defineProperties(Texture.prototype, { @@ -226,288 +509,6 @@ }); /** - * Updates this texture on the gpu. - * - */ -Texture.prototype.update = function () -{ - this.baseTexture.update(); -}; - -/** - * Called when the base texture is loaded - * - * @private - */ -Texture.prototype.onBaseTextureLoaded = function (baseTexture) -{ - this._updateID++; - - // TODO this code looks confusing.. boo to abusing getters and setterss! - if (this.noFrame) - { - this.frame = new math.Rectangle(0, 0, baseTexture.width, baseTexture.height); - } - else - { - this.frame = this._frame; - } - - this.baseTexture.on('update', this.onBaseTextureUpdated, this); - this.emit('update', this); - -}; - -/** - * Called when the base texture is updated - * - * @private - */ -Texture.prototype.onBaseTextureUpdated = function (baseTexture) -{ - this._updateID++; - - this._frame.width = baseTexture.width; - this._frame.height = baseTexture.height; - - this.emit('update', this); -}; - -/** - * Destroys this texture - * - * @param [destroyBase=false] {boolean} Whether to destroy the base texture as well - */ -Texture.prototype.destroy = function (destroyBase) -{ - if (this.baseTexture) - { - - if (destroyBase) - { - // delete the texture if it exists in the texture cache.. - // this only needs to be removed if the base texture is actually destoryed too.. - if(utils.TextureCache[this.baseTexture.imageUrl]) - { - delete utils.TextureCache[this.baseTexture.imageUrl]; - } - - this.baseTexture.destroy(); - } - - this.baseTexture.off('update', this.onBaseTextureUpdated, this); - this.baseTexture.off('loaded', this.onBaseTextureLoaded, this); - - this.baseTexture = null; - } - - this._frame = null; - this._uvs = null; - this.trim = null; - this.orig = null; - - this.valid = false; - - this.off('dispose', this.dispose, this); - this.off('update', this.update, this); -}; - -/** - * Creates a new texture object that acts the same as this one. - * - * @return {PIXI.Texture} - */ -Texture.prototype.clone = function () -{ - return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate); -}; - -/** - * Updates the internal WebGL UV cache. - * - * @protected - */ -Texture.prototype._updateUvs = function () -{ - if (!this._uvs) - { - this._uvs = new TextureUvs(); - } - - this._uvs.set(this._frame, this.baseTexture, this.rotate); - - this._updateID++; -}; - -/** - * Helper function that creates a Texture object from the given image url. - * If the image is not in the texture cache it will be created and loaded. - * - * @static - * @param imageUrl {string} The image url of the texture - * @param [crossorigin] {boolean} Whether requests should be treated as crossorigin - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromImage = function (imageUrl, crossorigin, scaleMode) -{ - var texture = utils.TextureCache[imageUrl]; - - if (!texture) - { - texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode)); - utils.TextureCache[imageUrl] = texture; - } - - return texture; -}; - -/** - * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromFrame = function (frameId) -{ - var texture = utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache'); - } - - return texture; -}; - -/** - * Helper function that creates a new Texture based on the given canvas element. - * - * @static - * @param canvas {HTMLCanvasElement} The canvas element source of the texture - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromCanvas = function (canvas, scaleMode) -{ - return new Texture(BaseTexture.fromCanvas(canvas, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the given video element. - * - * @static - * @param video {HTMLVideoElement|string} The URL or actual element of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideo = function (video, scaleMode) -{ - if (typeof video === 'string') - { - return Texture.fromVideoUrl(video, scaleMode); - } - else - { - return new Texture(VideoBaseTexture.fromVideo(video, scaleMode)); - } -}; - -/** - * Helper function that creates a new Texture based on the video url. - * - * @static - * @param videoUrl {string} URL of the video - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.Texture} The newly created texture - */ -Texture.fromVideoUrl = function (videoUrl, scaleMode) -{ - return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode)); -}; - -/** - * Helper function that creates a new Texture based on the source you provide. - * The soucre can be - frame id, image url, video url, canvae element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from - * @return {PIXI.Texture} The newly created texture - */ -Texture.from = function (source) -{ - //TODO auto detect cross origin.. - //TODO pass in scale mode? - if(typeof source === 'string') - { - var texture = utils.TextureCache[source]; - - if (!texture) - { - // check if its a video.. - var isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null; - if(isVideo) - { - return Texture.fromVideoUrl(source); - } - - return Texture.fromImage(source); - } - - return texture; - } - else if(source instanceof HTMLCanvasElement) - { - return Texture.fromCanvas(source); - } - else if(source instanceof HTMLVideoElement) - { - return Texture.fromVideo(source); - } - else if(source instanceof BaseTexture) - { - return new Texture(BaseTexture); - } - else - { - // lets assume its a texture! - return source; - } -}; - - -/** - * Adds a texture to the global utils.TextureCache. This cache is shared across the whole PIXI object. - * - * @static - * @param texture {PIXI.Texture} The Texture to add to the cache. - * @param id {string} The id that the texture will be stored against. - */ -Texture.addTextureToCache = function (texture, id) -{ - utils.TextureCache[id] = texture; -}; - -/** - * Remove a texture from the global utils.TextureCache. - * - * @static - * @param id {string} The id of the texture to be removed - * @return {PIXI.Texture} The texture that was removed - */ -Texture.removeTextureFromCache = function (id) -{ - var texture = utils.TextureCache[id]; - - delete utils.TextureCache[id]; - delete utils.BaseTextureCache[id]; - - return texture; -}; - -/** * An empty texture, used often to not have to create multiple empty textures. * Can not be destroyed. * diff --git a/src/core/textures/TextureUvs.js b/src/core/textures/TextureUvs.js index 8036744..139730c 100644 --- a/src/core/textures/TextureUvs.js +++ b/src/core/textures/TextureUvs.js @@ -1,3 +1,4 @@ +var GroupD8 = require('../math/GroupD8'); /** * A standard object to store the Uvs of a texture @@ -6,78 +7,79 @@ * @private * @memberof PIXI */ -function TextureUvs() -{ - this.x0 = 0; - this.y0 = 0; +class TextureUvs { + constructor() + { + this.x0 = 0; + this.y0 = 0; - this.x1 = 1; - this.y1 = 0; + this.x1 = 1; + this.y1 = 0; - this.x2 = 1; - this.y2 = 1; + this.x2 = 1; + this.y2 = 1; - this.x3 = 0; - this.y3 = 1; + this.x3 = 0; + this.y3 = 1; - this.uvsUint32 = new Uint32Array(4); + this.uvsUint32 = new Uint32Array(4); + } + + /** + * Sets the texture Uvs based on the given frame information + * @param frame {PIXI.Rectangle} + * @param baseFrame {PIXI.Rectangle} + * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} + * @private + */ + set(frame, baseFrame, rotate) + { + var tw = baseFrame.width; + var th = baseFrame.height; + + if(rotate) + { + //width and height div 2 div baseFrame size + var w2 = frame.width / 2 / tw; + var h2 = frame.height / 2 / th; + //coordinates of center + var cX = frame.x / tw + w2; + var cY = frame.y / th + h2; + rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner + this.x0 = cX + w2 * GroupD8.uX(rotate); + this.y0 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise + this.x1 = cX + w2 * GroupD8.uX(rotate); + this.y1 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x2 = cX + w2 * GroupD8.uX(rotate); + this.y2 = cY + h2 * GroupD8.uY(rotate); + rotate = GroupD8.add(rotate, 2); + this.x3 = cX + w2 * GroupD8.uX(rotate); + this.y3 = cY + h2 * GroupD8.uY(rotate); + } + else + { + + this.x0 = frame.x / tw; + this.y0 = frame.y / th; + + this.x1 = (frame.x + frame.width) / tw; + this.y1 = frame.y / th; + + this.x2 = (frame.x + frame.width) / tw; + this.y2 = (frame.y + frame.height) / th; + + this.x3 = frame.x / tw; + this.y3 = (frame.y + frame.height) / th; + } + + this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); + this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); + this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); + this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); + } + } module.exports = TextureUvs; - -var GroupD8 = require('../math/GroupD8'); - -/** - * Sets the texture Uvs based on the given frame information - * @param frame {PIXI.Rectangle} - * @param baseFrame {PIXI.Rectangle} - * @param rotate {number} Rotation of frame, see {@link PIXI.GroupD8} - * @private - */ -TextureUvs.prototype.set = function (frame, baseFrame, rotate) -{ - var tw = baseFrame.width; - var th = baseFrame.height; - - if(rotate) - { - //width and height div 2 div baseFrame size - var w2 = frame.width / 2 / tw; - var h2 = frame.height / 2 / th; - //coordinates of center - var cX = frame.x / tw + w2; - var cY = frame.y / th + h2; - rotate = GroupD8.add(rotate, GroupD8.NW); //NW is top-left corner - this.x0 = cX + w2 * GroupD8.uX(rotate); - this.y0 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); //rotate 90 degrees clockwise - this.x1 = cX + w2 * GroupD8.uX(rotate); - this.y1 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x2 = cX + w2 * GroupD8.uX(rotate); - this.y2 = cY + h2 * GroupD8.uY(rotate); - rotate = GroupD8.add(rotate, 2); - this.x3 = cX + w2 * GroupD8.uX(rotate); - this.y3 = cY + h2 * GroupD8.uY(rotate); - } - else - { - - this.x0 = frame.x / tw; - this.y0 = frame.y / th; - - this.x1 = (frame.x + frame.width) / tw; - this.y1 = frame.y / th; - - this.x2 = (frame.x + frame.width) / tw; - this.y2 = (frame.y + frame.height) / th; - - this.x3 = frame.x / tw; - this.y3 = (frame.y + frame.height) / th; - } - - this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); - this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); - this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); - this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); -}; diff --git a/src/core/textures/VideoBaseTexture.js b/src/core/textures/VideoBaseTexture.js index 1340d96..e2d47a9 100644 --- a/src/core/textures/VideoBaseTexture.js +++ b/src/core/textures/VideoBaseTexture.js @@ -29,200 +29,201 @@ * @param source {HTMLVideoElement} Video source * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values */ -function VideoBaseTexture(source, scaleMode) -{ - if (!source) +class VideoBaseTexture extends BaseTexture { + constructor(source, scaleMode) { - throw new Error('No video source element specified.'); + 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; + } + + super(source, scaleMode); + + /** + * Should the base texture automatically update itself, set to true by default + * + * @member {boolean} + * @default true + */ + this.autoUpdate = false; + + this._onUpdate = this._onUpdate.bind(this); + this._onCanPlay = this._onCanPlay.bind(this); + + if (!source.complete) + { + source.addEventListener('canplay', this._onCanPlay); + source.addEventListener('canplaythrough', this._onCanPlay); + + // started playing.. + source.addEventListener('play', this._onPlayStart.bind(this)); + source.addEventListener('pause', this._onPlayStop.bind(this)); + } + + this.__loaded = false; } - // 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); - /** - * Should the base texture automatically update itself, set to true by default + * The internal update loop of the video base texture, only runs when autoUpdate is set to true * - * @member {boolean} - * @default true + * @private */ - this.autoUpdate = false; - - this._onUpdate = this._onUpdate.bind(this); - this._onCanPlay = this._onCanPlay.bind(this); - - if (!source.complete) + _onUpdate() { - source.addEventListener('canplay', this._onCanPlay); - source.addEventListener('canplaythrough', this._onCanPlay); - - // started playing.. - source.addEventListener('play', this._onPlayStart.bind(this)); - source.addEventListener('pause', this._onPlayStop.bind(this)); + if (this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.update(); + } } - this.__loaded = false; + /** + * Runs the update loop when the video is ready to play + * + * @private + */ + _onPlayStart() + { + // Just in case the video has not recieved its can play even yet.. + if(!this.hasLoaded) + { + this._onCanPlay(); + } + + if (!this.autoUpdate) + { + window.requestAnimationFrame(this._onUpdate); + this.autoUpdate = true; + } + } + + /** + * Fired when a pause event is triggered, stops the update loop + * + * @private + */ + _onPlayStop() + { + this.autoUpdate = false; + } + + /** + * Fired when the video is loaded and ready to play + * + * @private + */ + _onCanPlay() + { + this.hasLoaded = true; + + if (this.source) + { + this.source.removeEventListener('canplay', this._onCanPlay); + this.source.removeEventListener('canplaythrough', this._onCanPlay); + + this.width = this.source.videoWidth; + this.height = this.source.videoHeight; + + this.source.play(); + + // prevent multiple loaded dispatches.. + if (!this.__loaded) + { + this.__loaded = true; + this.emit('loaded', this); + } + } + } + + /** + * Destroys this texture + * + */ + destroy() + { + if (this.source && this.source._pixiId) + { + delete utils.BaseTextureCache[ this.source._pixiId ]; + delete this.source._pixiId; + } + + super.destroy(); + } + + /** + * Mimic Pixi BaseTexture.from.... method. + * + * @static + * @param video {HTMLVideoElement} Video to create texture from + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromVideo(video, scaleMode) + { + if (!video._pixiId) + { + video._pixiId = 'video_' + utils.uid(); + } + + var baseTexture = utils.BaseTextureCache[video._pixiId]; + + if (!baseTexture) + { + baseTexture = new VideoBaseTexture(video, scaleMode); + utils.BaseTextureCache[ video._pixiId ] = baseTexture; + } + + return baseTexture; + } + + /** + * Helper function that creates a new BaseTexture based on the given video element. + * This BaseTexture can then be used to create a texture + * + * @static + * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. + * @param [videoSrc.src] {string} One of the source urls for the video + * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified + * the url's extension will be used as the second part of the mime type. + * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture + */ + static fromUrl(videoSrc, scaleMode) + { + var video = document.createElement('video'); + + // array of objects or strings + if (Array.isArray(videoSrc)) + { + for (var i = 0; i < videoSrc.length; ++i) + { + video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); + } + } + // single object or string + else + { + video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); + } + + video.load(); + video.play(); + + return VideoBaseTexture.fromVideo(video, scaleMode); + } + } -VideoBaseTexture.prototype = Object.create(BaseTexture.prototype); -VideoBaseTexture.prototype.constructor = VideoBaseTexture; module.exports = VideoBaseTexture; -/** - * The internal update loop of the video base texture, only runs when autoUpdate is set to true - * - * @private - */ -VideoBaseTexture.prototype._onUpdate = function () -{ - if (this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.update(); - } -}; - -/** - * Runs the update loop when the video is ready to play - * - * @private - */ -VideoBaseTexture.prototype._onPlayStart = function () -{ - // Just in case the video has not recieved its can play even yet.. - if(!this.hasLoaded) - { - this._onCanPlay(); - } - - if (!this.autoUpdate) - { - window.requestAnimationFrame(this._onUpdate); - this.autoUpdate = true; - } -}; - -/** - * Fired when a pause event is triggered, stops the update loop - * - * @private - */ -VideoBaseTexture.prototype._onPlayStop = function () -{ - this.autoUpdate = false; -}; - -/** - * Fired when the video is loaded and ready to play - * - * @private - */ -VideoBaseTexture.prototype._onCanPlay = function () -{ - this.hasLoaded = true; - - if (this.source) - { - this.source.removeEventListener('canplay', this._onCanPlay); - this.source.removeEventListener('canplaythrough', this._onCanPlay); - - this.width = this.source.videoWidth; - this.height = this.source.videoHeight; - - this.source.play(); - - // prevent multiple loaded dispatches.. - if (!this.__loaded) - { - this.__loaded = true; - this.emit('loaded', this); - } - } -}; - -/** - * Destroys this texture - * - */ -VideoBaseTexture.prototype.destroy = function () -{ - if (this.source && this.source._pixiId) - { - delete utils.BaseTextureCache[ this.source._pixiId ]; - delete this.source._pixiId; - } - - BaseTexture.prototype.destroy.call(this); -}; - -/** - * Mimic Pixi BaseTexture.from.... method. - * - * @static - * @param video {HTMLVideoElement} Video to create texture from - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromVideo = function (video, scaleMode) -{ - if (!video._pixiId) - { - video._pixiId = 'video_' + utils.uid(); - } - - var baseTexture = utils.BaseTextureCache[video._pixiId]; - - if (!baseTexture) - { - baseTexture = new VideoBaseTexture(video, scaleMode); - utils.BaseTextureCache[ video._pixiId ] = baseTexture; - } - - return baseTexture; -}; - -/** - * Helper function that creates a new BaseTexture based on the given video element. - * This BaseTexture can then be used to create a texture - * - * @static - * @param videoSrc {string|object|string[]|object[]} The URL(s) for the video. - * @param [videoSrc.src] {string} One of the source urls for the video - * @param [videoSrc.mime] {string} The mimetype of the video (e.g. 'video/mp4'). If not specified - * the url's extension will be used as the second part of the mime type. - * @param scaleMode {number} See {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.VideoBaseTexture} Newly created VideoBaseTexture - */ -VideoBaseTexture.fromUrl = function (videoSrc, scaleMode) -{ - var video = document.createElement('video'); - - // array of objects or strings - if (Array.isArray(videoSrc)) - { - for (var i = 0; i < videoSrc.length; ++i) - { - video.appendChild(createSource(videoSrc[i].src || videoSrc[i], videoSrc[i].mime)); - } - } - // single object or string - else - { - video.appendChild(createSource(videoSrc.src || videoSrc, videoSrc.mime)); - } - - video.load(); - video.play(); - - return VideoBaseTexture.fromVideo(video, scaleMode); -}; - VideoBaseTexture.fromUrls = VideoBaseTexture.fromUrl; function createSource(path, type) diff --git a/src/core/ticker/Ticker.js b/src/core/ticker/Ticker.js index 2a4bda5..f036a87 100644 --- a/src/core/ticker/Ticker.js +++ b/src/core/ticker/Ticker.js @@ -13,9 +13,98 @@ * @class * @memberof PIXI.ticker */ -function Ticker() -{ - var _this = this; +class Ticker { + constructor() + { + /** + * Internal emitter used to fire 'tick' event + * @private + */ + this._emitter = new EventEmitter(); + + /** + * Internal current frame request ID + * @private + */ + this._requestId = null; + + /** + * Internal value managed by minFPS property setter and getter. + * This is the maximum allowed milliseconds between updates. + * @private + */ + this._maxElapsedMS = 100; + + /** + * Whether or not this ticker should invoke the method + * {@link PIXI.ticker.Ticker#start} automatically + * when a listener is added. + * + * @member {boolean} + * @default false + */ + this.autoStart = false; + + /** + * Scalar time value from last frame to this frame. + * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} + * and is scaled with {@link PIXI.ticker.Ticker#speed}. + * **Note:** The cap may be exceeded by scaling. + * + * @member {number} + * @default 1 + */ + this.deltaTime = 1; + + /** + * Time elapsed in milliseconds from last frame to this frame. + * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} + * is based, this value is neither capped nor scaled. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 1 / TARGET_FPMS + */ + this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + + /** + * The last time {@link PIXI.ticker.Ticker#update} was invoked. + * This value is also reset internally outside of invoking + * update, but only when a new animation frame is requested. + * If the platform supports DOMHighResTimeStamp, + * this value will have a precision of 1 µs. + * + * @member {number} + * @default 0 + */ + this.lastTime = 0; + + /** + * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. + * @example + * // Scales ticker.deltaTime to what would be + * // the equivalent of approximately 120 FPS + * ticker.speed = 2; + * + * @member {number} + * @default 1 + */ + this.speed = 1; + + /** + * Whether or not this ticker has been started. + * `true` if {@link PIXI.ticker.Ticker#start} has been called. + * `false` if {@link PIXI.ticker.Ticker#stop} has been called. + * While `false`, this value may change to `true` in the + * event of {@link PIXI.ticker.Ticker#autoStart} being `true` + * and a listener is added. + * + * @member {boolean} + * @default false + */ + this.started = false; + } /** * Internal tick method bound to ticker instance. @@ -27,7 +116,9 @@ * * @private */ - this._tick = function _tick(time) { + _tick(time) { + + var _this = this; _this._requestId = null; @@ -41,96 +132,203 @@ _this._requestId = requestAnimationFrame(_this._tick); } } - }; + } /** - * Internal emitter used to fire 'tick' event + * Conditionally requests a new animation frame. + * If a frame has not already been requested, and if the internal + * emitter has listeners, a new frame is requested. + * * @private */ - this._emitter = new EventEmitter(); + _requestIfNeeded() + { + if (this._requestId === null && this._emitter.listeners(TICK, true)) + { + // ensure callbacks get correct delta + this.lastTime = performance.now(); + this._requestId = requestAnimationFrame(this._tick); + } + } /** - * Internal current frame request ID + * Conditionally cancels a pending animation frame. + * * @private */ - this._requestId = null; + _cancelIfNeeded() + { + if (this._requestId !== null) + { + cancelAnimationFrame(this._requestId); + this._requestId = null; + } + } /** - * Internal value managed by minFPS property setter and getter. - * This is the maximum allowed milliseconds between updates. + * Conditionally requests a new animation frame. + * If the ticker has been started it checks if a frame has not already + * been requested, and if the internal emitter has listeners. If these + * conditions are met, a new frame is requested. If the ticker has not + * been started, but autoStart is `true`, then the ticker starts now, + * and continues with the previous conditions to request a new frame. + * * @private */ - this._maxElapsedMS = 100; + _startIfPossible() + { + if (this.started) + { + this._requestIfNeeded(); + } + else if (this.autoStart) + { + this.start(); + } + } /** - * Whether or not this ticker should invoke the method - * {@link PIXI.ticker.Ticker#start} automatically - * when a listener is added. + * Calls {@link module:eventemitter3.EventEmitter#on} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {boolean} - * @default false + * @param fn {Function} The listener function to be added for updates + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.autoStart = false; + add(fn, context) + { + this._emitter.on(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Scalar time value from last frame to this frame. - * This value is capped by setting {@link PIXI.ticker.Ticker#minFPS} - * and is scaled with {@link PIXI.ticker.Ticker#speed}. - * **Note:** The cap may be exceeded by scaling. + * Calls {@link module:eventemitter3.EventEmitter#once} internally for the + * internal 'tick' event. It checks if the emitter has listeners, + * and if so it requests a new animation frame at this point. * - * @member {number} - * @default 1 + * @param fn {Function} The listener function to be added for one update + * @param [context] {Function} The listener context + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.deltaTime = 1; + addOnce(fn, context) + { + this._emitter.once(TICK, fn, context); + + this._startIfPossible(); + + return this; + } /** - * Time elapsed in milliseconds from last frame to this frame. - * Opposed to what the scalar {@link PIXI.ticker.Ticker#deltaTime} - * is based, this value is neither capped nor scaled. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. + * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. + * It checks if the emitter has listeners for 'tick' event. + * If it does, then it cancels the animation frame. * - * @member {number} - * @default 1 / TARGET_FPMS + * @param [fn] {Function} The listener function to be removed + * @param [context] {Function} The listener context to be removed + * @returns {PIXI.ticker.Ticker} This instance of a ticker */ - this.elapsedMS = 1 / CONST.TARGET_FPMS; // default to target frame time + remove(fn, context) + { + this._emitter.off(TICK, fn, context); + + if (!this._emitter.listeners(TICK, true)) + { + this._cancelIfNeeded(); + } + + return this; + } /** - * The last time {@link PIXI.ticker.Ticker#update} was invoked. - * This value is also reset internally outside of invoking - * update, but only when a new animation frame is requested. - * If the platform supports DOMHighResTimeStamp, - * this value will have a precision of 1 µs. - * - * @member {number} - * @default 0 + * Starts the ticker. If the ticker has listeners + * a new animation frame is requested at this point. */ - this.lastTime = 0; + start() + { + if (!this.started) + { + this.started = true; + this._requestIfNeeded(); + } + } /** - * Factor of current {@link PIXI.ticker.Ticker#deltaTime}. - * @example - * // Scales ticker.deltaTime to what would be - * // the equivalent of approximately 120 FPS - * ticker.speed = 2; - * - * @member {number} - * @default 1 + * Stops the ticker. If the ticker has requested + * an animation frame it is canceled at this point. */ - this.speed = 1; + stop() + { + if (this.started) + { + this.started = false; + this._cancelIfNeeded(); + } + } /** - * Whether or not this ticker has been started. - * `true` if {@link PIXI.ticker.Ticker#start} has been called. - * `false` if {@link PIXI.ticker.Ticker#stop} has been called. - * While `false`, this value may change to `true` in the - * event of {@link PIXI.ticker.Ticker#autoStart} being `true` - * and a listener is added. + * Triggers an update. An update entails setting the + * current {@link PIXI.ticker.Ticker#elapsedMS}, + * the current {@link PIXI.ticker.Ticker#deltaTime}, + * invoking all listeners with current deltaTime, + * and then finally setting {@link PIXI.ticker.Ticker#lastTime} + * with the value of currentTime that was provided. + * This method will be called automatically by animation + * frame callbacks if the ticker instance has been started + * and listeners are added. * - * @member {boolean} - * @default false + * @param [currentTime=performance.now()] {number} the current time of execution */ - this.started = false; + update(currentTime) + { + var elapsedMS; + + // Allow calling update directly with default currentTime. + currentTime = currentTime || performance.now(); + + // If the difference in time is zero or negative, we ignore most of the work done here. + // If there is no valid difference, then should be no reason to let anyone know about it. + // A zero delta, is exactly that, nothing should update. + // + // The difference in time can be negative, and no this does not mean time traveling. + // This can be the result of a race condition between when an animation frame is requested + // on the current JavaScript engine event loop, and when the ticker's start method is invoked + // (which invokes the internal _requestIfNeeded method). If a frame is requested before + // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, + // can receive a time argument that can be less than the lastTime value that was set within + // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. + // + // This check covers this browser engine timing issue, as well as if consumers pass an invalid + // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. + + if (currentTime > this.lastTime) + { + // Save uncapped elapsedMS for measurement + elapsedMS = this.elapsedMS = currentTime - this.lastTime; + + // cap the milliseconds elapsed used for deltaTime + if (elapsedMS > this._maxElapsedMS) + { + elapsedMS = this._maxElapsedMS; + } + + this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; + + // Invoke listeners added to internal emitter + this._emitter.emit(TICK, this.deltaTime); + } + else + { + this.deltaTime = this.elapsedMS = 0; + } + + this.lastTime = currentTime; + } + } Object.defineProperties(Ticker.prototype, { @@ -176,199 +374,4 @@ } }); -/** - * Conditionally requests a new animation frame. - * If a frame has not already been requested, and if the internal - * emitter has listeners, a new frame is requested. - * - * @private - */ -Ticker.prototype._requestIfNeeded = function _requestIfNeeded() -{ - if (this._requestId === null && this._emitter.listeners(TICK, true)) - { - // ensure callbacks get correct delta - this.lastTime = performance.now(); - this._requestId = requestAnimationFrame(this._tick); - } -}; - -/** - * Conditionally cancels a pending animation frame. - * - * @private - */ -Ticker.prototype._cancelIfNeeded = function _cancelIfNeeded() -{ - if (this._requestId !== null) - { - cancelAnimationFrame(this._requestId); - this._requestId = null; - } -}; - -/** - * Conditionally requests a new animation frame. - * If the ticker has been started it checks if a frame has not already - * been requested, and if the internal emitter has listeners. If these - * conditions are met, a new frame is requested. If the ticker has not - * been started, but autoStart is `true`, then the ticker starts now, - * and continues with the previous conditions to request a new frame. - * - * @private - */ -Ticker.prototype._startIfPossible = function _startIfPossible() -{ - if (this.started) - { - this._requestIfNeeded(); - } - else if (this.autoStart) - { - this.start(); - } -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#on} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for updates - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.add = function add(fn, context) -{ - this._emitter.on(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#once} internally for the - * internal 'tick' event. It checks if the emitter has listeners, - * and if so it requests a new animation frame at this point. - * - * @param fn {Function} The listener function to be added for one update - * @param [context] {Function} The listener context - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.addOnce = function addOnce(fn, context) -{ - this._emitter.once(TICK, fn, context); - - this._startIfPossible(); - - return this; -}; - -/** - * Calls {@link module:eventemitter3.EventEmitter#off} internally for 'tick' event. - * It checks if the emitter has listeners for 'tick' event. - * If it does, then it cancels the animation frame. - * - * @param [fn] {Function} The listener function to be removed - * @param [context] {Function} The listener context to be removed - * @returns {PIXI.ticker.Ticker} This instance of a ticker - */ -Ticker.prototype.remove = function remove(fn, context) -{ - this._emitter.off(TICK, fn, context); - - if (!this._emitter.listeners(TICK, true)) - { - this._cancelIfNeeded(); - } - - return this; -}; - -/** - * Starts the ticker. If the ticker has listeners - * a new animation frame is requested at this point. - */ -Ticker.prototype.start = function start() -{ - if (!this.started) - { - this.started = true; - this._requestIfNeeded(); - } -}; - -/** - * Stops the ticker. If the ticker has requested - * an animation frame it is canceled at this point. - */ -Ticker.prototype.stop = function stop() -{ - if (this.started) - { - this.started = false; - this._cancelIfNeeded(); - } -}; - -/** - * Triggers an update. An update entails setting the - * current {@link PIXI.ticker.Ticker#elapsedMS}, - * the current {@link PIXI.ticker.Ticker#deltaTime}, - * invoking all listeners with current deltaTime, - * and then finally setting {@link PIXI.ticker.Ticker#lastTime} - * with the value of currentTime that was provided. - * This method will be called automatically by animation - * frame callbacks if the ticker instance has been started - * and listeners are added. - * - * @param [currentTime=performance.now()] {number} the current time of execution - */ -Ticker.prototype.update = function update(currentTime) -{ - var elapsedMS; - - // Allow calling update directly with default currentTime. - currentTime = currentTime || performance.now(); - - // If the difference in time is zero or negative, we ignore most of the work done here. - // If there is no valid difference, then should be no reason to let anyone know about it. - // A zero delta, is exactly that, nothing should update. - // - // The difference in time can be negative, and no this does not mean time traveling. - // This can be the result of a race condition between when an animation frame is requested - // on the current JavaScript engine event loop, and when the ticker's start method is invoked - // (which invokes the internal _requestIfNeeded method). If a frame is requested before - // _requestIfNeeded is invoked, then the callback for the animation frame the ticker requests, - // can receive a time argument that can be less than the lastTime value that was set within - // _requestIfNeeded. This difference is in microseconds, but this is enough to cause problems. - // - // This check covers this browser engine timing issue, as well as if consumers pass an invalid - // currentTime value. This may happen if consumers opt-out of the autoStart, and update themselves. - - if (currentTime > this.lastTime) - { - // Save uncapped elapsedMS for measurement - elapsedMS = this.elapsedMS = currentTime - this.lastTime; - - // cap the milliseconds elapsed used for deltaTime - if (elapsedMS > this._maxElapsedMS) - { - elapsedMS = this._maxElapsedMS; - } - - this.deltaTime = elapsedMS * CONST.TARGET_FPMS * this.speed; - - // Invoke listeners added to internal emitter - this._emitter.emit(TICK, this.deltaTime); - } - else - { - this.deltaTime = this.elapsedMS = 0; - } - - this.lastTime = currentTime; -}; - module.exports = Ticker; diff --git a/src/extract/canvas/CanvasExtract.js b/src/extract/canvas/CanvasExtract.js index 3185cac..0897c51 100644 --- a/src/extract/canvas/CanvasExtract.js +++ b/src/extract/canvas/CanvasExtract.js @@ -7,144 +7,145 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; +class CanvasExtract { + constructor(renderer) + { + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = this.renderer.width; + frame.height = this.renderer.height; + } + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); + canvasBuffer.context.putImageData(canvasData, 0, 0); + + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var context; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = renderer.generateTexture(target); + } + } + + if(renderTexture) + { + context = renderTexture.baseTexture._canvasRenderTarget.context; + resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; + frame = renderTexture.frame; + } + else + { + context = renderer.rootContext; + resolution = renderer.rootResolution; + + frame = tempRect; + frame.width = renderer.width; + frame.height = renderer.height; + } + + return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; + } + + /** + * Destroys the extract + * + */ + destroy() + { + this.renderer.extract = null; + this.renderer = null; + } + } - -CanvasExtract.prototype.constructor = CanvasExtract; module.exports = CanvasExtract; -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -CanvasExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling CanvasExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -CanvasExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -CanvasExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = this.renderer.width; - frame.height = this.renderer.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - var canvasData = context.getImageData(frame.x * resolution, frame.y * resolution, width, height); - canvasBuffer.context.putImageData(canvasData, 0, 0); - - - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -CanvasExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var context; - var resolution; - var frame; - var renderTexture; - - if(target) - { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = renderer.generateTexture(target); - } - } - - if(renderTexture) - { - context = renderTexture.baseTexture._canvasRenderTarget.context; - resolution = renderTexture.baseTexture._canvasRenderTarget.resolution; - frame = renderTexture.frame; - } - else - { - context = renderer.rootContext; - resolution = renderer.rootResolution; - - frame = tempRect; - frame.width = renderer.width; - frame.height = renderer.height; - } - - return context.getImageData(0, 0, frame.width * resolution, frame.height * resolution).data; -}; - -/** - * Destroys the extract - * - */ -CanvasExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; - core.CanvasRenderer.registerPlugin('extract', CanvasExtract); diff --git a/src/extract/webgl/WebGLExtract.js b/src/extract/webgl/WebGLExtract.js index 519dade..aabc2d2 100644 --- a/src/extract/webgl/WebGLExtract.js +++ b/src/extract/webgl/WebGLExtract.js @@ -7,189 +7,190 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLExtract(renderer) -{ - this.renderer = renderer; - renderer.extract = this; -} - - -WebGLExtract.prototype.constructor = WebGLExtract; -module.exports = WebGLExtract; - -/** - * Will return a HTML Image of the target - * - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLImageElement} HTML Image of the target - */ -WebGLExtract.prototype.image = function ( target ) -{ - var image = new Image(); - image.src = this.base64( target ); - return image; -}; - -/** - * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {string} A base64 encoded string of the texture. - */ -WebGLExtract.prototype.base64 = function ( target ) -{ - return this.canvas( target ).toDataURL(); -}; - -/** - * Creates a Canvas element, renders this target to it and then returns it. - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. - */ -WebGLExtract.prototype.canvas = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var flipY = false; - var renderTexture; - - if(target) +class WebGLExtract { + constructor(renderer) { - if(target instanceof core.RenderTexture) + this.renderer = renderer; + renderer.extract = this; + } + + /** + * Will return a HTML Image of the target + * + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLImageElement} HTML Image of the target + */ + image( target ) + { + var image = new Image(); + image.src = this.base64( target ); + return image; + } + + /** + * Will return a a base64 encoded string of this target. It works by calling WebGLExtract.getCanvas and then running toDataURL on that. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {string} A base64 encoded string of the texture. + */ + base64( target ) + { + return this.canvas( target ).toDataURL(); + } + + /** + * Creates a Canvas element, renders this target to it and then returns it. + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {HTMLCanvasElement} A Canvas element with the texture rendered on. + */ + canvas( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var flipY = false; + var renderTexture; + + if(target) { - renderTexture = target; + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + flipY = false; + } + else + { + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + flipY = true; + + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; + + } + + + + var width = frame.width * resolution; + var height = frame.height * resolution; + + var canvasBuffer = new core.CanvasRenderTarget(width, height); + + if(textureBuffer) + { + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + + // set up an array of pixels + var webGLPixels = new Uint8Array(4 * width * height); + + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); + + // add the pixels to the canvas + var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); + canvasData.data.set(webGLPixels); + + canvasBuffer.context.putImageData(canvasData, 0, 0); + + // pulling pixels + if(flipY) + { + canvasBuffer.context.scale(1, -1); + canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + } + } + + // send the canvas back.. + return canvasBuffer.canvas; + } + + /** + * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). + * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer + * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture + */ + pixels( target ) + { + var renderer = this.renderer; + var textureBuffer; + var resolution; + var frame; + var renderTexture; + + if(target) + { + if(target instanceof core.RenderTexture) + { + renderTexture = target; + } + else + { + renderTexture = this.renderer.generateTexture(target); + } + } + + if(renderTexture) + { + textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; + resolution = textureBuffer.resolution; + frame = renderTexture.frame; + } else { - renderTexture = this.renderer.generateTexture(target); + textureBuffer = this.renderer.rootRenderTarget; + resolution = textureBuffer.resolution; + frame = tempRect; + frame.width = textureBuffer.size.width; + frame.height = textureBuffer.size.height; } - } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; - flipY = false; - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - flipY = true; + var width = frame.width * resolution; + var height = frame.height * resolution; - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - - } - - - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var canvasBuffer = new core.CanvasRenderTarget(width, height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - - // set up an array of pixels var webGLPixels = new Uint8Array(4 * width * height); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - - // add the pixels to the canvas - var canvasData = canvasBuffer.context.getImageData(0, 0, width, height); - canvasData.data.set(webGLPixels); - - canvasBuffer.context.putImageData(canvasData, 0, 0); - - // pulling pixels - if(flipY) + if(textureBuffer) { - canvasBuffer.context.scale(1, -1); - canvasBuffer.context.drawImage(canvasBuffer.canvas, 0,-height); + // bind the buffer + renderer.bindRenderTarget(textureBuffer); + // read pixels to the array + var gl = renderer.gl; + gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); } + + return webGLPixels; } - // send the canvas back.. - return canvasBuffer.canvas; -}; - -/** - * Will return a one-dimensional array containing the pixel data of the entire texture in RGBA order, with integer values between 0 and 255 (included). - * @param target {PIXI.DisplayObject|PIXI.RenderTexture} A displayObject or renderTexture to convert. If left empty will use use the main renderer - * @return {Uint8ClampedArray} One-dimensional array containing the pixel data of the entire texture - */ -WebGLExtract.prototype.pixels = function ( target ) -{ - var renderer = this.renderer; - var textureBuffer; - var resolution; - var frame; - var renderTexture; - - if(target) + /** + * Destroys the extract + * + */ + destroy() { - if(target instanceof core.RenderTexture) - { - renderTexture = target; - } - else - { - renderTexture = this.renderer.generateTexture(target); - } + this.renderer.extract = null; + this.renderer = null; } - if(renderTexture) - { - textureBuffer = renderTexture.baseTexture._glRenderTargets[this.renderer.CONTEXT_UID]; - resolution = textureBuffer.resolution; - frame = renderTexture.frame; +} - } - else - { - textureBuffer = this.renderer.rootRenderTarget; - resolution = textureBuffer.resolution; - - frame = tempRect; - frame.width = textureBuffer.size.width; - frame.height = textureBuffer.size.height; - } - - var width = frame.width * resolution; - var height = frame.height * resolution; - - var webGLPixels = new Uint8Array(4 * width * height); - - if(textureBuffer) - { - // bind the buffer - renderer.bindRenderTarget(textureBuffer); - // read pixels to the array - var gl = renderer.gl; - gl.readPixels(frame.x * resolution, frame.y * resolution, width, height, gl.RGBA, gl.UNSIGNED_BYTE, webGLPixels); - } - - return webGLPixels; -}; - -/** - * Destroys the extract - * - */ -WebGLExtract.prototype.destroy = function () -{ - this.renderer.extract = null; - this.renderer = null; -}; +module.exports = WebGLExtract; core.WebGLRenderer.registerPlugin('extract', WebGLExtract); diff --git a/src/extras/BitmapText.js b/src/extras/BitmapText.js index b0be469..e7d9a05 100644 --- a/src/extras/BitmapText.js +++ b/src/extras/BitmapText.js @@ -28,103 +28,290 @@ * single line text * @param [style.tint=0xFFFFFF] {number} The tint color */ -function BitmapText(text, style) -{ - core.Container.call(this); +class BitmapText extends core.Container { + constructor(text, style) + { + super(); - style = style || {}; + style = style || {}; + + /** + * The width of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textWidth = 0; + + /** + * The height of the overall text, different from fontSize, + * which is defined in the style object + * + * @member {number} + * @readonly + */ + this.textHeight = 0; + + /** + * Private tracker for the letter sprite pool. + * + * @member {PIXI.Sprite[]} + * @private + */ + this._glyphs = []; + + /** + * Private tracker for the current style. + * + * @member {object} + * @private + */ + this._font = { + tint: style.tint !== undefined ? style.tint : 0xFFFFFF, + align: style.align || 'left', + name: null, + size: 0 + }; + + /** + * Private tracker for the current font. + * + * @member {object} + * @private + */ + this.font = style.font; // run font setter + + /** + * Private tracker for the current text. + * + * @member {string} + * @private + */ + this._text = text; + + /** + * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. + * Disable by setting value to 0 + * + * @member {number} + */ + this.maxWidth = 0; + + /** + * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. + * + * @member {number} + */ + this.maxLineHeight = 0; + + /** + * Text anchor. read-only + * + * @member {PIXI.ObservablePoint} + * @private + */ + this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); + + /** + * The dirty state of this object. + * + * @member {boolean} + */ + this.dirty = false; + + this.updateText(); + } /** - * The width of the overall text, different from fontSize, - * which is defined in the style object + * Renders text and updates it when needed * - * @member {number} - * @readonly - */ - this.textWidth = 0; - - /** - * The height of the overall text, different from fontSize, - * which is defined in the style object - * - * @member {number} - * @readonly - */ - this.textHeight = 0; - - /** - * Private tracker for the letter sprite pool. - * - * @member {PIXI.Sprite[]} * @private */ - this._glyphs = []; + updateText() + { + var data = BitmapText.fonts[this._font.name]; + var pos = new core.Point(); + var prevCharCode = null; + var chars = []; + var lastLineWidth = 0; + var maxLineWidth = 0; + var lineWidths = []; + var line = 0; + var scale = this._font.size / data.size; + var lastSpace = -1; + var lastSpaceWidth = 0; + var maxLineHeight = 0; + + for (var i = 0; i < this.text.length; i++) + { + var charCode = this.text.charCodeAt(i); + + if(/(\s)/.test(this.text.charAt(i))){ + lastSpace = i; + lastSpaceWidth = lastLineWidth; + } + + if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) + { + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) + { + core.utils.removeItems(chars, lastSpace, i - lastSpace); + i = lastSpace; + lastSpace = -1; + + lineWidths.push(lastSpaceWidth); + maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); + line++; + + pos.x = 0; + pos.y += data.lineHeight; + prevCharCode = null; + continue; + } + + var charData = data.chars[charCode]; + + if (!charData) + { + continue; + } + + if (prevCharCode && charData.kerning[prevCharCode]) + { + pos.x += charData.kerning[prevCharCode]; + } + + chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); + lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); + pos.x += charData.xAdvance; + maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); + prevCharCode = charCode; + } + + lineWidths.push(lastLineWidth); + maxLineWidth = Math.max(maxLineWidth, lastLineWidth); + + var lineAlignOffsets = []; + + for (i = 0; i <= line; i++) + { + var alignOffset = 0; + + if (this._font.align === 'right') + { + alignOffset = maxLineWidth - lineWidths[i]; + } + else if (this._font.align === 'center') + { + alignOffset = (maxLineWidth - lineWidths[i]) / 2; + } + + lineAlignOffsets.push(alignOffset); + } + + var lenChars = chars.length; + var tint = this.tint; + + for (i = 0; i < lenChars; i++) + { + var c = this._glyphs[i]; // get the next glyph sprite + + if (c) + { + c.texture = chars[i].texture; + } + else + { + c = new core.Sprite(chars[i].texture); + this._glyphs.push(c); + } + + c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; + c.position.y = chars[i].position.y * scale; + c.scale.x = c.scale.y = scale; + c.tint = tint; + + if (!c.parent) + { + this.addChild(c); + } + } + + // remove unnecessary children. + for (i = lenChars; i < this._glyphs.length; ++i) + { + this.removeChild(this._glyphs[i]); + } + + this.textWidth = maxLineWidth * scale; + this.textHeight = (pos.y + data.lineHeight) * scale; + + // apply anchor + if (this.anchor.x !== 0 || this.anchor.y !== 0) + { + for (i = 0; i < lenChars; i++) + { + this._glyphs[i].x -= this.textWidth * this.anchor.x; + this._glyphs[i].y -= this.textHeight * this.anchor.y; + } + } + this.maxLineHeight = maxLineHeight * scale; + } /** - * Private tracker for the current style. + * Updates the transform of this object * - * @member {object} * @private */ - this._font = { - tint: style.tint !== undefined ? style.tint : 0xFFFFFF, - align: style.align || 'left', - name: null, - size: 0 - }; + updateTransform() + { + this.validate(); + this.containerUpdateTransform(); + } /** - * Private tracker for the current font. + * Validates text before calling parent's getLocalBounds * - * @member {object} + * @return {PIXI.Rectangle} The rectangular bounding area + */ + + getLocalBounds() + { + this.validate(); + return super.getLocalBounds(); + } + + /** + * Updates text when needed + * * @private */ - this.font = style.font; // run font setter + validate() + { + if (this.dirty) + { + this.updateText(); + this.dirty = false; + } + } - /** - * Private tracker for the current text. - * - * @member {string} - * @private - */ - this._text = text; + makeDirty() { + this.dirty = true; + } - /** - * The max width of this bitmap text in pixels. If the text provided is longer than the value provided, line breaks will be automatically inserted in the last whitespace. - * Disable by setting value to 0 - * - * @member {number} - */ - this.maxWidth = 0; - - /** - * The max line height. This is useful when trying to use the total height of the Text, ie: when trying to vertically align. - * - * @member {number} - */ - this.maxLineHeight = 0; - - /** - * Text anchor. read-only - * - * @member {PIXI.ObservablePoint} - * @private - */ - this._anchor = new ObservablePoint(this.makeDirty, this, 0, 0); - - /** - * The dirty state of this object. - * - * @member {boolean} - */ - this.dirty = false; - - this.updateText(); } -// constructor -BitmapText.prototype = Object.create(core.Container.prototype); -BitmapText.prototype.constructor = BitmapText; module.exports = BitmapText; Object.defineProperties(BitmapText.prototype, { @@ -246,191 +433,4 @@ } }); -/** - * Renders text and updates it when needed - * - * @private - */ -BitmapText.prototype.updateText = function () -{ - var data = BitmapText.fonts[this._font.name]; - var pos = new core.Point(); - var prevCharCode = null; - var chars = []; - var lastLineWidth = 0; - var maxLineWidth = 0; - var lineWidths = []; - var line = 0; - var scale = this._font.size / data.size; - var lastSpace = -1; - var lastSpaceWidth = 0; - var maxLineHeight = 0; - - for (var i = 0; i < this.text.length; i++) - { - var charCode = this.text.charCodeAt(i); - - if(/(\s)/.test(this.text.charAt(i))){ - lastSpace = i; - lastSpaceWidth = lastLineWidth; - } - - if (/(?:\r\n|\r|\n)/.test(this.text.charAt(i))) - { - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - if (lastSpace !== -1 && this.maxWidth > 0 && pos.x * scale > this.maxWidth) - { - core.utils.removeItems(chars, lastSpace, i - lastSpace); - i = lastSpace; - lastSpace = -1; - - lineWidths.push(lastSpaceWidth); - maxLineWidth = Math.max(maxLineWidth, lastSpaceWidth); - line++; - - pos.x = 0; - pos.y += data.lineHeight; - prevCharCode = null; - continue; - } - - var charData = data.chars[charCode]; - - if (!charData) - { - continue; - } - - if (prevCharCode && charData.kerning[prevCharCode]) - { - pos.x += charData.kerning[prevCharCode]; - } - - chars.push({texture:charData.texture, line: line, charCode: charCode, position: new core.Point(pos.x + charData.xOffset, pos.y + charData.yOffset)}); - lastLineWidth = pos.x + (charData.texture.width + charData.xOffset); - pos.x += charData.xAdvance; - maxLineHeight = Math.max(maxLineHeight, (charData.yOffset + charData.texture.height)); - prevCharCode = charCode; - } - - lineWidths.push(lastLineWidth); - maxLineWidth = Math.max(maxLineWidth, lastLineWidth); - - var lineAlignOffsets = []; - - for (i = 0; i <= line; i++) - { - var alignOffset = 0; - - if (this._font.align === 'right') - { - alignOffset = maxLineWidth - lineWidths[i]; - } - else if (this._font.align === 'center') - { - alignOffset = (maxLineWidth - lineWidths[i]) / 2; - } - - lineAlignOffsets.push(alignOffset); - } - - var lenChars = chars.length; - var tint = this.tint; - - for (i = 0; i < lenChars; i++) - { - var c = this._glyphs[i]; // get the next glyph sprite - - if (c) - { - c.texture = chars[i].texture; - } - else - { - c = new core.Sprite(chars[i].texture); - this._glyphs.push(c); - } - - c.position.x = (chars[i].position.x + lineAlignOffsets[chars[i].line]) * scale; - c.position.y = chars[i].position.y * scale; - c.scale.x = c.scale.y = scale; - c.tint = tint; - - if (!c.parent) - { - this.addChild(c); - } - } - - // remove unnecessary children. - for (i = lenChars; i < this._glyphs.length; ++i) - { - this.removeChild(this._glyphs[i]); - } - - this.textWidth = maxLineWidth * scale; - this.textHeight = (pos.y + data.lineHeight) * scale; - - // apply anchor - if (this.anchor.x !== 0 || this.anchor.y !== 0) - { - for (i = 0; i < lenChars; i++) - { - this._glyphs[i].x -= this.textWidth * this.anchor.x; - this._glyphs[i].y -= this.textHeight * this.anchor.y; - } - } - this.maxLineHeight = maxLineHeight * scale; -}; - -/** - * Updates the transform of this object - * - * @private - */ -BitmapText.prototype.updateTransform = function () -{ - this.validate(); - this.containerUpdateTransform(); -}; - -/** - * Validates text before calling parent's getLocalBounds - * - * @return {PIXI.Rectangle} The rectangular bounding area - */ - -BitmapText.prototype.getLocalBounds = function() -{ - this.validate(); - return core.Container.prototype.getLocalBounds.call(this); -}; - -/** - * Updates text when needed - * - * @private - */ -BitmapText.prototype.validate = function() -{ - if (this.dirty) - { - this.updateText(); - this.dirty = false; - } -}; - -BitmapText.prototype.makeDirty = function() { - this.dirty = true; -}; - BitmapText.fonts = {}; diff --git a/src/extras/MovieClip.js b/src/extras/MovieClip.js index 212efa9..32fa2f8 100644 --- a/src/extras/MovieClip.js +++ b/src/extras/MovieClip.js @@ -29,66 +29,231 @@ * @memberof PIXI.extras * @param textures {PIXI.Texture[]|FrameObject[]} an array of {@link PIXI.Texture} or frame objects that make up the animation */ -function MovieClip(textures) -{ - core.Sprite.call(this, textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); +class MovieClip extends core.Sprite { + constructor(textures) + { + super(textures[0] instanceof core.Texture ? textures[0] : textures[0].texture); + + /** + * @private + */ + this._textures = null; + + /** + * @private + */ + this._durations = null; + + this.textures = textures; + + /** + * The speed that the MovieClip will play at. Higher is faster, lower is slower + * + * @member {number} + * @default 1 + */ + this.animationSpeed = 1; + + /** + * Whether or not the movie clip repeats after playing. + * + * @member {boolean} + * @default true + */ + this.loop = true; + + /** + * Function to call when a MovieClip finishes playing + * + * @method + * @memberof PIXI.extras.MovieClip# + */ + this.onComplete = null; + + /** + * Elapsed time since animation has been started, used internally to display current texture + * + * @member {number} + * @private + */ + this._currentTime = 0; + + /** + * Indicates if the MovieClip is currently playing + * + * @member {boolean} + * @readonly + */ + this.playing = false; + } /** + * Stops the MovieClip + * + */ + stop() + { + if(!this.playing) + { + return; + } + + this.playing = false; + core.ticker.shared.remove(this.update, this); + } + + /** + * Plays the MovieClip + * + */ + play() + { + if(this.playing) + { + return; + } + + this.playing = true; + core.ticker.shared.add(this.update, this); + } + + /** + * Stops the MovieClip and goes to a specific frame + * + * @param frameNumber {number} frame index to stop at + */ + gotoAndStop(frameNumber) + { + this.stop(); + + this._currentTime = frameNumber; + + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + /** + * Goes to a specific frame and begins playing the MovieClip + * + * @param frameNumber {number} frame index to start at + */ + gotoAndPlay(frameNumber) + { + this._currentTime = frameNumber; + + this.play(); + } + + /* + * Updates the object transform for rendering * @private */ - this._textures = null; + update(deltaTime) + { + var elapsed = this.animationSpeed * deltaTime; - /** - * @private - */ - this._durations = null; + if (this._durations !== null) + { + var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - this.textures = textures; + lag += elapsed / 60 * 1000; - /** - * The speed that the MovieClip will play at. Higher is faster, lower is slower + while (lag < 0) + { + this._currentTime--; + lag += this._durations[this.currentFrame]; + } + + var sign = Math.sign(this.animationSpeed * deltaTime); + this._currentTime = Math.floor(this._currentTime); + + while (lag >= this._durations[this.currentFrame]) + { + lag -= this._durations[this.currentFrame] * sign; + this._currentTime += sign; + } + + this._currentTime += lag / this._durations[this.currentFrame]; + } + else + { + this._currentTime += elapsed; + } + + if (this._currentTime < 0 && !this.loop) + { + this.gotoAndStop(0); + + if (this.onComplete) + { + this.onComplete(); + } + } + else if (this._currentTime >= this._textures.length && !this.loop) + { + this.gotoAndStop(this._textures.length - 1); + + if (this.onComplete) + { + this.onComplete(); + } + } + else + { + this._texture = this._textures[this.currentFrame]; + this._textureID = -1; + } + + } + + /* + * Stops the MovieClip and destroys it * - * @member {number} - * @default 1 */ - this.animationSpeed = 1; + destroy( ) + { + this.stop(); + super.destroy(); + } /** - * Whether or not the movie clip repeats after playing. + * A short hand way of creating a movieclip from an array of frame ids * - * @member {boolean} - * @default true + * @static + * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames */ - this.loop = true; + static fromFrames(frames) + { + var textures = []; + + for (var i = 0; i < frames.length; ++i) + { + textures.push(core.Texture.fromFrame(frames[i])); + } + + return new MovieClip(textures); + } /** - * Function to call when a MovieClip finishes playing + * A short hand way of creating a movieclip from an array of image ids * - * @method - * @memberof PIXI.extras.MovieClip# + * @static + * @param images {string[]} the array of image urls the movieclip will use as its texture frames */ - this.onComplete = null; + static fromImages(images) + { + var textures = []; - /** - * Elapsed time since animation has been started, used internally to display current texture - * - * @member {number} - * @private - */ - this._currentTime = 0; + for (var i = 0; i < images.length; ++i) + { + textures.push(core.Texture.fromImage(images[i])); + } - /** - * Indicates if the MovieClip is currently playing - * - * @member {boolean} - * @readonly - */ - this.playing = false; + return new MovieClip(textures); + } + } -// constructor -MovieClip.prototype = Object.create(core.Sprite.prototype); -MovieClip.prototype.constructor = MovieClip; module.exports = MovieClip; Object.defineProperties(MovieClip.prototype, { @@ -160,168 +325,3 @@ } }); - -/** - * Stops the MovieClip - * - */ -MovieClip.prototype.stop = function () -{ - if(!this.playing) - { - return; - } - - this.playing = false; - core.ticker.shared.remove(this.update, this); -}; - -/** - * Plays the MovieClip - * - */ -MovieClip.prototype.play = function () -{ - if(this.playing) - { - return; - } - - this.playing = true; - core.ticker.shared.add(this.update, this); -}; - -/** - * Stops the MovieClip and goes to a specific frame - * - * @param frameNumber {number} frame index to stop at - */ -MovieClip.prototype.gotoAndStop = function (frameNumber) -{ - this.stop(); - - this._currentTime = frameNumber; - - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; -}; - -/** - * Goes to a specific frame and begins playing the MovieClip - * - * @param frameNumber {number} frame index to start at - */ -MovieClip.prototype.gotoAndPlay = function (frameNumber) -{ - this._currentTime = frameNumber; - - this.play(); -}; - -/* - * Updates the object transform for rendering - * @private - */ -MovieClip.prototype.update = function (deltaTime) -{ - var elapsed = this.animationSpeed * deltaTime; - - if (this._durations !== null) - { - var lag = this._currentTime % 1 * this._durations[this.currentFrame]; - - lag += elapsed / 60 * 1000; - - while (lag < 0) - { - this._currentTime--; - lag += this._durations[this.currentFrame]; - } - - var sign = Math.sign(this.animationSpeed * deltaTime); - this._currentTime = Math.floor(this._currentTime); - - while (lag >= this._durations[this.currentFrame]) - { - lag -= this._durations[this.currentFrame] * sign; - this._currentTime += sign; - } - - this._currentTime += lag / this._durations[this.currentFrame]; - } - else - { - this._currentTime += elapsed; - } - - if (this._currentTime < 0 && !this.loop) - { - this.gotoAndStop(0); - - if (this.onComplete) - { - this.onComplete(); - } - } - else if (this._currentTime >= this._textures.length && !this.loop) - { - this.gotoAndStop(this._textures.length - 1); - - if (this.onComplete) - { - this.onComplete(); - } - } - else - { - this._texture = this._textures[this.currentFrame]; - this._textureID = -1; - } - -}; - -/* - * Stops the MovieClip and destroys it - * - */ -MovieClip.prototype.destroy = function ( ) -{ - this.stop(); - core.Sprite.prototype.destroy.call(this); -}; - -/** - * A short hand way of creating a movieclip from an array of frame ids - * - * @static - * @param frames {string[]} the array of frames ids the movieclip will use as its texture frames - */ -MovieClip.fromFrames = function (frames) -{ - var textures = []; - - for (var i = 0; i < frames.length; ++i) - { - textures.push(core.Texture.fromFrame(frames[i])); - } - - return new MovieClip(textures); -}; - -/** - * A short hand way of creating a movieclip from an array of image ids - * - * @static - * @param images {string[]} the array of image urls the movieclip will use as its texture frames - */ -MovieClip.fromImages = function (images) -{ - var textures = []; - - for (var i = 0; i < images.length; ++i) - { - textures.push(core.Texture.fromImage(images[i])); - } - - return new MovieClip(textures); -}; diff --git a/src/extras/TilingSprite.js b/src/extras/TilingSprite.js index ea8af0e..df2d9e5 100644 --- a/src/extras/TilingSprite.js +++ b/src/extras/TilingSprite.js @@ -14,61 +14,410 @@ * @param width {number} the width of the tiling sprite * @param height {number} the height of the tiling sprite */ -function TilingSprite(texture, width, height) -{ - core.Sprite.call(this, texture); +class TilingSprite extends core.Sprite { + constructor(texture, width, height) + { + super(texture); - /** - * The scaling of the image that is being tiled - * - * @member {PIXI.Point} - */ - this.tileScale = new core.Point(1,1); + /** + * The scaling of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tileScale = new core.Point(1,1); + + + /** + * The offset position of the image that is being tiled + * + * @member {PIXI.Point} + */ + this.tilePosition = new core.Point(0,0); + + ///// private + + /** + * The with of the tiling sprite + * + * @member {number} + * @private + */ + this._width = width || 100; + + /** + * The height of the tiling sprite + * + * @member {number} + * @private + */ + this._height = height || 100; + + /** + * An internal WebGL UV cache. + * + * @member {PIXI.TextureUvs} + * @private + */ + this._uvs = new core.TextureUvs(); + + this._canvasPattern = null; + + this._glDatas = []; + } + + _onTextureUpdate() + { + return; + } /** - * The offset position of the image that is being tiled + * Renders the object using the WebGL renderer * - * @member {PIXI.Point} - */ - this.tilePosition = new core.Point(0,0); - - ///// private - - /** - * The with of the tiling sprite - * - * @member {number} + * @param renderer {PIXI.WebGLRenderer} The renderer * @private */ - this._width = width || 100; + _renderWebGL(renderer) + { + + // tweak our texture temporarily.. + var texture = this._texture; + + if(!texture || !texture._uvs) + { + return; + } + + // get rid of any thing that may be batching. + renderer.flush(); + + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new TilingShader(gl), + quad:new core.Quad(gl) + }; + + this._glDatas[renderer.CONTEXT_UID] = glData; + + glData.quad.initVao(glData.shader); + } + + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + var vertices = glData.quad.vertices; + + vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; + vertices[1] = vertices[3] = this._height * -this.anchor.y; + + vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); + vertices[5] = vertices[7] = this._height * (1-this.anchor.y); + + glData.quad.upload(); + + renderer.bindShader(glData.shader); + + var textureUvs = texture._uvs, + textureWidth = texture._frame.width, + textureHeight = texture._frame.height, + textureBaseWidth = texture.baseTexture.width, + textureBaseHeight = texture.baseTexture.height; + + var uPixelSize = glData.shader.uniforms.uPixelSize; + uPixelSize[0] = 1.0/textureBaseWidth; + uPixelSize[1] = 1.0/textureBaseHeight; + glData.shader.uniforms.uPixelSize = uPixelSize; + + var uFrame = glData.shader.uniforms.uFrame; + uFrame[0] = textureUvs.x0; + uFrame[1] = textureUvs.y0; + uFrame[2] = textureUvs.x1 - textureUvs.x0; + uFrame[3] = textureUvs.y2 - textureUvs.y0; + glData.shader.uniforms.uFrame = uFrame; + + var uTransform = glData.shader.uniforms.uTransform; + uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; + uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; + uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; + uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; + glData.shader.uniforms.uTransform = uTransform; + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + + var color = tempArray; + + core.utils.hex2rgb(this.tint, color); + color[3] = this.worldAlpha; + + glData.shader.uniforms.uColor = color; + + renderer.bindTexture(this._texture, 0); + + renderer.state.setBlendMode( this.blendMode ); + glData.quad.draw(); + } /** - * The height of the tiling sprite + * Renders the object using the Canvas renderer * - * @member {number} + * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer * @private */ - this._height = height || 100; + _renderCanvas(renderer) + { + var texture = this._texture; + + if (!texture.baseTexture.hasLoaded) + { + return; + } + + var context = renderer.context, + transform = this.worldTransform, + resolution = renderer.resolution, + baseTexture = texture.baseTexture, + modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, + modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; + + // create a nice shiny pattern! + // TODO this needs to be refreshed if texture changes.. + if(!this._canvasPattern) + { + // cut an object from a spritesheet.. + var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); + + // Tint the tiling sprite + if (this.tint !== 0xFFFFFF) + { + if (this.cachedTint !== this.tint) + { + this.cachedTint = this.tint; + + this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); + } + tempCanvas.context.drawImage(this.tintedTexture, 0, 0); + } + else + { + tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); + } + this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); + } + + // set context state.. + context.globalAlpha = this.worldAlpha; + context.setTransform(transform.a * resolution, + transform.b * resolution, + transform.c * resolution, + transform.d * resolution, + transform.tx * resolution, + transform.ty * resolution); + + // TODO - this should be rolled into the setTransform above.. + context.scale(this.tileScale.x,this.tileScale.y); + + context.translate(modX + (this.anchor.x * -this._width ), + modY + (this.anchor.y * -this._height)); + + // check blend mode + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== renderer.context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + // fill the pattern! + context.fillStyle = this._canvasPattern; + context.fillRect(-modX, + -modY, + this._width / this.tileScale.x, + this._height / this.tileScale.y); + + + //TODO - pretty sure this can be deleted... + //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); + //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); + } + /** - * An internal WebGL UV cache. - * - * @member {PIXI.TextureUvs} - * @private + * Returns the framing rectangle of the sprite as a Rectangle object + * + * @return {PIXI.Rectangle} the framing rectangle */ - this._uvs = new core.TextureUvs(); + getBounds() + { + var width = this._width; + var height = this._height; - this._canvasPattern = null; + var w0 = width * (1-this.anchor.x); + var w1 = width * -this.anchor.x; - this._glDatas = []; + var h0 = height * (1-this.anchor.y); + var h1 = height * -this.anchor.y; + + var worldTransform = 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 minX, + maxX, + minY, + maxY; + + minX = x1; + minX = x2 < minX ? x2 : minX; + minX = x3 < minX ? x3 : minX; + minX = x4 < minX ? x4 : minX; + + minY = y1; + minY = y2 < minY ? y2 : minY; + minY = y3 < minY ? y3 : minY; + minY = y4 < minY ? y4 : minY; + + maxX = x1; + maxX = x2 > maxX ? x2 : maxX; + maxX = x3 > maxX ? x3 : maxX; + maxX = x4 > maxX ? x4 : maxX; + + maxY = y1; + maxY = y2 > maxY ? y2 : maxY; + maxY = y3 > maxY ? y3 : maxY; + maxY = y4 > maxY ? y4 : maxY; + + var bounds = this._bounds; + + bounds.x = minX; + bounds.width = maxX - minX; + + bounds.y = minY; + bounds.height = maxY - minY; + + // store a reference so that if this function gets called again in the render cycle we do not have to recalculate + this._currentBounds = bounds; + + return bounds; + } + + /** + * Checks if a point is inside this tiling sprite + * @param point {PIXI.Point} the point to check + */ + containsPoint( point ) + { + this.worldTransform.applyInverse(point, tempPoint); + + var width = this._width; + var height = this._height; + var x1 = -width * this.anchor.x; + var y1; + + if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) + { + y1 = -height * this.anchor.y; + + if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) + { + return true; + } + } + + return false; + } + + /** + * Destroys this tiling sprite + * + */ + destroy() { + super.destroy(); + + this.tileScale = null; + this._tileScaleOffset = null; + this.tilePosition = null; + + this._uvs = null; + } + + /** + * Helper function that creates a new tiling sprite based on the source you provide. + * The source can be - frame id, image url, video url, canvas element, video element, base texture + * + * @static + * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.Texture} The newly created texture + */ + static from(source,width,height) + { + return new TilingSprite(Texture.from(source),width,height); + } + + + /** + * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId + * The frame ids are created when a Texture packer file has been loaded + * + * @static + * @param frameId {string} The frame Id of the texture in the cache + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @return {PIXI.extras.TilingSprite} A new TilingSprite + */ + static fromFrame(frameId,width,height) + { + var texture = core.utils.TextureCache[frameId]; + + if (!texture) + { + throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); + } + + return new TilingSprite(texture,width,height); + } + + /** + * Helper function that creates a sprite that will contain a texture based on an image url + * If the image is not in the texture cache it will be loaded + * + * @static + * @param imageId {string} The image url of the texture + * @param width {number} the width of the tiling sprite + * @param height {number} the height of the tiling sprite + * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter + * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values + * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id + */ + static fromImage(imageId, width, height, crossorigin, scaleMode) + { + return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); + } + } -TilingSprite.prototype = Object.create(core.Sprite.prototype); -TilingSprite.prototype.constructor = TilingSprite; module.exports = TilingSprite; - Object.defineProperties(TilingSprite.prototype, { /** * The width of the sprite, setting this will actually modify the scale to achieve the value set @@ -104,353 +453,3 @@ } } }); - -TilingSprite.prototype._onTextureUpdate = function () -{ - return; -}; - - -/** - * Renders the object using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The renderer - * @private - */ -TilingSprite.prototype._renderWebGL = function (renderer) -{ - - // tweak our texture temporarily.. - var texture = this._texture; - - if(!texture || !texture._uvs) - { - return; - } - - // get rid of any thing that may be batching. - renderer.flush(); - - var gl = renderer.gl; - var glData = this._glDatas[renderer.CONTEXT_UID]; - - if(!glData) - { - glData = { - shader:new TilingShader(gl), - quad:new core.Quad(gl) - }; - - this._glDatas[renderer.CONTEXT_UID] = glData; - - glData.quad.initVao(glData.shader); - } - - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - var vertices = glData.quad.vertices; - - vertices[0] = vertices[6] = ( this._width ) * -this.anchor.x; - vertices[1] = vertices[3] = this._height * -this.anchor.y; - - vertices[2] = vertices[4] = ( this._width ) * (1-this.anchor.x); - vertices[5] = vertices[7] = this._height * (1-this.anchor.y); - - glData.quad.upload(); - - renderer.bindShader(glData.shader); - - var textureUvs = texture._uvs, - textureWidth = texture._frame.width, - textureHeight = texture._frame.height, - textureBaseWidth = texture.baseTexture.width, - textureBaseHeight = texture.baseTexture.height; - - var uPixelSize = glData.shader.uniforms.uPixelSize; - uPixelSize[0] = 1.0/textureBaseWidth; - uPixelSize[1] = 1.0/textureBaseHeight; - glData.shader.uniforms.uPixelSize = uPixelSize; - - var uFrame = glData.shader.uniforms.uFrame; - uFrame[0] = textureUvs.x0; - uFrame[1] = textureUvs.y0; - uFrame[2] = textureUvs.x1 - textureUvs.x0; - uFrame[3] = textureUvs.y2 - textureUvs.y0; - glData.shader.uniforms.uFrame = uFrame; - - var uTransform = glData.shader.uniforms.uTransform; - uTransform[0] = (this.tilePosition.x % (textureWidth * this.tileScale.x)) / this._width; - uTransform[1] = (this.tilePosition.y % (textureHeight * this.tileScale.y)) / this._height; - uTransform[2] = ( textureBaseWidth / this._width ) * this.tileScale.x; - uTransform[3] = ( textureBaseHeight / this._height ) * this.tileScale.y; - glData.shader.uniforms.uTransform = uTransform; - - glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); - - var color = tempArray; - - core.utils.hex2rgb(this.tint, color); - color[3] = this.worldAlpha; - - glData.shader.uniforms.uColor = color; - - renderer.bindTexture(this._texture, 0); - - renderer.state.setBlendMode( this.blendMode ); - glData.quad.draw(); -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} a reference to the canvas renderer - * @private - */ -TilingSprite.prototype._renderCanvas = function (renderer) -{ - var texture = this._texture; - - if (!texture.baseTexture.hasLoaded) - { - return; - } - - var context = renderer.context, - transform = this.worldTransform, - resolution = renderer.resolution, - baseTexture = texture.baseTexture, - modX = (this.tilePosition.x / this.tileScale.x) % texture._frame.width, - modY = (this.tilePosition.y / this.tileScale.y) % texture._frame.height; - - // create a nice shiny pattern! - // TODO this needs to be refreshed if texture changes.. - if(!this._canvasPattern) - { - // cut an object from a spritesheet.. - var tempCanvas = new core.CanvasRenderTarget(texture._frame.width, texture._frame.height); - - // Tint the tiling sprite - if (this.tint !== 0xFFFFFF) - { - if (this.cachedTint !== this.tint) - { - this.cachedTint = this.tint; - - this.tintedTexture = CanvasTinter.getTintedTexture(this, this.tint); - } - tempCanvas.context.drawImage(this.tintedTexture, 0, 0); - } - else - { - tempCanvas.context.drawImage(baseTexture.source, -texture._frame.x, -texture._frame.y); - } - this._canvasPattern = tempCanvas.context.createPattern( tempCanvas.canvas, 'repeat' ); - } - - // set context state.. - context.globalAlpha = this.worldAlpha; - context.setTransform(transform.a * resolution, - transform.b * resolution, - transform.c * resolution, - transform.d * resolution, - transform.tx * resolution, - transform.ty * resolution); - - // TODO - this should be rolled into the setTransform above.. - context.scale(this.tileScale.x,this.tileScale.y); - - context.translate(modX + (this.anchor.x * -this._width ), - modY + (this.anchor.y * -this._height)); - - // check blend mode - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== renderer.context.globalCompositeOperation) - { - context.globalCompositeOperation = compositeOperation; - } - - // fill the pattern! - context.fillStyle = this._canvasPattern; - context.fillRect(-modX, - -modY, - this._width / this.tileScale.x, - this._height / this.tileScale.y); - - - //TODO - pretty sure this can be deleted... - //context.translate(-this.tilePosition.x + (this.anchor.x * this._width), -this.tilePosition.y + (this.anchor.y * this._height)); - //context.scale(1 / this.tileScale.x, 1 / this.tileScale.y); -}; - - -/** - * Returns the framing rectangle of the sprite as a Rectangle object -* - * @return {PIXI.Rectangle} the framing rectangle - */ -TilingSprite.prototype.getBounds = function () -{ - var width = this._width; - var height = this._height; - - var w0 = width * (1-this.anchor.x); - var w1 = width * -this.anchor.x; - - var h0 = height * (1-this.anchor.y); - var h1 = height * -this.anchor.y; - - var worldTransform = 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 minX, - maxX, - minY, - maxY; - - minX = x1; - minX = x2 < minX ? x2 : minX; - minX = x3 < minX ? x3 : minX; - minX = x4 < minX ? x4 : minX; - - minY = y1; - minY = y2 < minY ? y2 : minY; - minY = y3 < minY ? y3 : minY; - minY = y4 < minY ? y4 : minY; - - maxX = x1; - maxX = x2 > maxX ? x2 : maxX; - maxX = x3 > maxX ? x3 : maxX; - maxX = x4 > maxX ? x4 : maxX; - - maxY = y1; - maxY = y2 > maxY ? y2 : maxY; - maxY = y3 > maxY ? y3 : maxY; - maxY = y4 > maxY ? y4 : maxY; - - var bounds = this._bounds; - - bounds.x = minX; - bounds.width = maxX - minX; - - bounds.y = minY; - bounds.height = maxY - minY; - - // store a reference so that if this function gets called again in the render cycle we do not have to recalculate - this._currentBounds = bounds; - - return bounds; -}; - -/** - * Checks if a point is inside this tiling sprite - * @param point {PIXI.Point} the point to check - */ -TilingSprite.prototype.containsPoint = function( point ) -{ - this.worldTransform.applyInverse(point, tempPoint); - - var width = this._width; - var height = this._height; - var x1 = -width * this.anchor.x; - var y1; - - if ( tempPoint.x > x1 && tempPoint.x < x1 + width ) - { - y1 = -height * this.anchor.y; - - if ( tempPoint.y > y1 && tempPoint.y < y1 + height ) - { - return true; - } - } - - return false; -}; - -/** - * Destroys this tiling sprite - * - */ -TilingSprite.prototype.destroy = function () { - core.Sprite.prototype.destroy.call(this); - - this.tileScale = null; - this._tileScaleOffset = null; - this.tilePosition = null; - - this._uvs = null; -}; - -/** - * Helper function that creates a new tiling sprite based on the source you provide. - * The source can be - frame id, image url, video url, canvas element, video element, base texture - * - * @static - * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from -* @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.Texture} The newly created texture - */ -TilingSprite.from = function (source,width,height) -{ - return new TilingSprite(Texture.from(source),width,height); -}; - - - -/** - * Helper function that creates a tiling sprite that will use a texture from the TextureCache based on the frameId - * The frame ids are created when a Texture packer file has been loaded - * - * @static - * @param frameId {string} The frame Id of the texture in the cache - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the frameId - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @return {PIXI.extras.TilingSprite} A new TilingSprite - */ -TilingSprite.fromFrame = function (frameId,width,height) -{ - var texture = core.utils.TextureCache[frameId]; - - if (!texture) - { - throw new Error('The frameId "' + frameId + '" does not exist in the texture cache ' + this); - } - - return new TilingSprite(texture,width,height); -}; - -/** - * Helper function that creates a sprite that will contain a texture based on an image url - * If the image is not in the texture cache it will be loaded - * - * @static - * @param imageId {string} The image url of the texture - * @param width {number} the width of the tiling sprite - * @param height {number} the height of the tiling sprite - * @param [crossorigin=(auto)] {boolean} if you want to specify the cross-origin parameter - * @param [scaleMode=PIXI.SCALE_MODES.DEFAULT] {number} if you want to specify the scale mode, see {@link PIXI.SCALE_MODES} for possible values - * @return {PIXI.extras.TilingSprite} A new TilingSprite using a texture from the texture cache matching the image id - */ -TilingSprite.fromImage = function (imageId, width, height, crossorigin, scaleMode) -{ - return new TilingSprite(core.Texture.fromImage(imageId, crossorigin, scaleMode),width,height); -}; diff --git a/src/extras/webgl/TilingShader.js b/src/extras/webgl/TilingShader.js index ef1337f..86c8e63 100644 --- a/src/extras/webgl/TilingShader.js +++ b/src/extras/webgl/TilingShader.js @@ -7,16 +7,15 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} The WebGL shader manager this shader works for. */ -function TilingShader(gl) -{ - Shader.call(this, - gl, - glslify('./tilingSprite.vert'), - glslify('./tilingSprite.frag') - ); +class TilingShader extends Shader { + constructor(gl) + { + super( + gl, + glslify('./tilingSprite.vert'), + glslify('./tilingSprite.frag') + ); + } } -TilingShader.prototype = Object.create(Shader.prototype); -TilingShader.prototype.constructor = TilingShader; module.exports = TilingShader; - diff --git a/src/filters/blur/BlurFilter.js b/src/filters/blur/BlurFilter.js index 0b72620..1aca693 100644 --- a/src/filters/blur/BlurFilter.js +++ b/src/filters/blur/BlurFilter.js @@ -10,35 +10,36 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurFilter(strength, quality, resolution) -{ - core.Filter.call(this); +class BlurFilter extends core.Filter { + constructor(strength, quality, resolution) + { + super(); - this.blurXFilter = new BlurXFilter(); - this.blurYFilter = new BlurYFilter(); - this.resolution = 1; + this.blurXFilter = new BlurXFilter(); + this.blurYFilter = new BlurYFilter(); + this.resolution = 1; - this.padding = 0; - this.resolution = resolution || 1; - this.quality = quality || 4; - this.blur = strength || 8; + this.padding = 0; + this.resolution = resolution || 1; + this.quality = quality || 4; + this.blur = strength || 8; + } + + apply(filterManager, input, output) + { + + var renderTarget = filterManager.getRenderTarget(true); + + this.blurXFilter.apply(filterManager, input, renderTarget, true); + this.blurYFilter.apply(filterManager, renderTarget, output, false); + + filterManager.returnRenderTarget(renderTarget); + } + } -BlurFilter.prototype = Object.create(core.Filter.prototype); -BlurFilter.prototype.constructor = BlurFilter; module.exports = BlurFilter; -BlurFilter.prototype.apply = function (filterManager, input, output) -{ - - var renderTarget = filterManager.getRenderTarget(true); - - this.blurXFilter.apply(filterManager, input, renderTarget, true); - this.blurYFilter.apply(filterManager, renderTarget, output, false); - - filterManager.returnRenderTarget(renderTarget); -}; - Object.defineProperties(BlurFilter.prototype, { /** * Sets the strength of both the blurX and blurY properties simultaneously diff --git a/src/filters/blur/BlurXFilter.js b/src/filters/blur/BlurXFilter.js index d6a0e8c..74bdf39 100644 --- a/src/filters/blur/BlurXFilter.js +++ b/src/filters/blur/BlurXFilter.js @@ -10,78 +10,78 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurXFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, true); - var fragSrc = generateBlurFragSource(5); +class BlurXFilter extends core.Filter { + constructor(strength, quality, resolution) + { + var vertSrc = generateBlurVertSource(5, true); + var fragSrc = generateBlurFragSource(5); - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.resolution = resolution || 1; + this.resolution = resolution || 1; - this._quality = 0; + this._quality = 0; - this.quality = quality || 4; - this.strength = strength || 8; + this.quality = quality || 4; + this.strength = strength || 8; - this.firstRun = true; + this.firstRun = true; + + } + + apply(filterManager, input, output, clear) + { + if(this.firstRun) + { + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); + + this.vertexSrc = generateBlurVertSource(kernelSize, true); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; + } + + this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + // screen space! + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } + } } -BlurXFilter.prototype = Object.create(core.Filter.prototype); -BlurXFilter.prototype.constructor = BlurXFilter; module.exports = BlurXFilter; -BlurXFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) - { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); - - this.vertexSrc = generateBlurVertSource(kernelSize, true); - this.fragmentSrc = generateBlurFragSource(kernelSize); - - this.firstRun = false; - } - - this.uniforms.strength = (1/output.size.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - // screen space! - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes;// / this.passes//Math.pow(1, this.passes); - - if(this.passes === 1) - { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) - { - filterManager.applyFilter(this, flip, flop, true); - - var temp = flop; - flop = flip; - flip = temp; - } - - filterManager.applyFilter(this, flip, output, clear); - - filterManager.returnRenderTarget(renderTarget); - } -}; - - Object.defineProperties(BlurXFilter.prototype, { /** * Sets the strength of both the blur. diff --git a/src/filters/blur/BlurYFilter.js b/src/filters/blur/BlurYFilter.js index f8bbf1a..b18bb04 100644 --- a/src/filters/blur/BlurYFilter.js +++ b/src/filters/blur/BlurYFilter.js @@ -10,75 +10,75 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function BlurYFilter(strength, quality, resolution) -{ - var vertSrc = generateBlurVertSource(5, false); - var fragSrc = generateBlurFragSource(5); - - core.Filter.call(this, - // vertex shader - vertSrc, - // fragment shader - fragSrc - ); - - this.resolution = resolution || 1; - - this._quality = 0; - - this.quality = quality || 4; - this.strength = strength || 8; - - this.firstRun = true; -} - -BlurYFilter.prototype = Object.create(core.Filter.prototype); -BlurYFilter.prototype.constructor = BlurYFilter; -module.exports = BlurYFilter; - -BlurYFilter.prototype.apply = function (filterManager, input, output, clear) -{ - if(this.firstRun) +class BlurYFilter extends core.Filter { + constructor(strength, quality, resolution) { - var gl = filterManager.renderer.gl; - var kernelSize = getMaxBlurKernelSize(gl); + var vertSrc = generateBlurVertSource(5, false); + var fragSrc = generateBlurFragSource(5); - this.vertexSrc = generateBlurVertSource(kernelSize, false); - this.fragmentSrc = generateBlurFragSource(kernelSize); + super( + // vertex shader + vertSrc, + // fragment shader + fragSrc + ); - this.firstRun = false; + this.resolution = resolution || 1; + + this._quality = 0; + + this.quality = quality || 4; + this.strength = strength || 8; + + this.firstRun = true; } - this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.strength *= this.strength; - this.uniforms.strength /= this.passes; - - if(this.passes === 1) + apply(filterManager, input, output, clear) { - filterManager.applyFilter(this, input, output, clear); - } - else - { - var renderTarget = filterManager.getRenderTarget(true); - var flip = input; - var flop = renderTarget; - - for(var i = 0; i < this.passes-1; i++) + if(this.firstRun) { - filterManager.applyFilter(this, flip, flop, true); + var gl = filterManager.renderer.gl; + var kernelSize = getMaxBlurKernelSize(gl); - var temp = flop; - flop = flip; - flip = temp; + this.vertexSrc = generateBlurVertSource(kernelSize, false); + this.fragmentSrc = generateBlurFragSource(kernelSize); + + this.firstRun = false; } - filterManager.applyFilter(this, flip, output, clear); + this.uniforms.strength = (1/output.size.height) * (output.size.height/input.size.height); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - filterManager.returnRenderTarget(renderTarget); + this.uniforms.strength *= this.strength; + this.uniforms.strength /= this.passes; + + if(this.passes === 1) + { + filterManager.applyFilter(this, input, output, clear); + } + else + { + var renderTarget = filterManager.getRenderTarget(true); + var flip = input; + var flop = renderTarget; + + for(var i = 0; i < this.passes-1; i++) + { + filterManager.applyFilter(this, flip, flop, true); + + var temp = flop; + flop = flip; + flip = temp; + } + + filterManager.applyFilter(this, flip, output, clear); + + filterManager.returnRenderTarget(renderTarget); + } } -}; +} + +module.exports = BlurYFilter; Object.defineProperties(BlurYFilter.prototype, { /** diff --git a/src/filters/colormatrix/ColorMatrixFilter.js b/src/filters/colormatrix/ColorMatrixFilter.js index 91b500e..f476511 100644 --- a/src/filters/colormatrix/ColorMatrixFilter.js +++ b/src/filters/colormatrix/ColorMatrixFilter.js @@ -17,522 +17,523 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function ColorMatrixFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./colorMatrix.frag') - ); +class ColorMatrixFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./colorMatrix.frag') + ); - this.uniforms.m = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0]; + this.uniforms.m = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0]; + } + + + /** + * Transforms current matrix and set the new one + * + * @param matrix {number[]} (mat 5x4) + * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix + */ + _loadMatrix(matrix, multiply) + { + multiply = !!multiply; + + var newMatrix = matrix; + + if (multiply) { + this._multiply(newMatrix, this.uniforms.m, matrix); + newMatrix = this._colorMatrix(newMatrix); + } + + // set the new matrix + this.uniforms.m = newMatrix; + } + + /** + * Multiplies two mat5's + * + * @param out {number[]} (mat 5x4) the receiving matrix + * @param a {number[]} (mat 5x4) the first operand + * @param b {number[]} (mat 5x4) the second operand + * @returns out {number[]} (mat 5x4) + */ + _multiply(out, a, b) + { + + // Red Channel + out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); + out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); + out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); + out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); + out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); + + // Green Channel + out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); + out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); + out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); + out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); + out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); + + // Blue Channel + out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); + out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); + out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); + out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); + out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); + + // Alpha Channel + out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); + out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); + out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); + out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); + out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); + + return out; + } + + /** + * Create a Float32 Array and normalize the offset component to 0-1 + * + * @param matrix {number[]} (mat 5x4) + * @return m {number[]} (mat 5x4) with all values between 0-1 + */ + _colorMatrix(matrix) + { + // Create a Float32 Array and normalize the offset component to 0-1 + var m = new Float32Array(matrix); + m[4] /= 255; + m[9] /= 255; + m[14] /= 255; + m[19] /= 255; + + return m; + } + + /** + * Adjusts brightness + * + * @param b {number} value of the brigthness (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + brightness(b, multiply) + { + var matrix = [ + b, 0, 0, 0, 0, + 0, b, 0, 0, 0, + 0, 0, b, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the matrices in grey scales + * + * @param scale {number} value of the grey (0 is black) + * @param multiply {boolean} refer to ._loadMatrix() method + */ + greyscale(scale, multiply) + { + var matrix = [ + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + scale, scale, scale, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the black and white matrice + * Multiply the current matrix + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + blackAndWhite(multiply) + { + var matrix = [ + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0.3, 0.6, 0.1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the hue property of the color + * + * @param rotation {number} in degrees + * @param multiply {boolean} refer to ._loadMatrix() method + */ + hue(rotation, multiply) + { + rotation = (rotation || 0) / 180 * Math.PI; + + var cosR = Math.cos(rotation), + sinR = Math.sin(rotation), + sqrt = Math.sqrt; + + /*a good approximation for hue rotation + This matrix is far better than the versions with magic luminance constants + formerly used here, but also used in the starling framework (flash) and known from this + old part of the internet: quasimondo.com/archives/000565.php + + This new matrix is based on rgb cube rotation in space. Look here for a more descriptive + implementation as a shader not a general matrix: + https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js + + This is the source for the code: + see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 + */ + + var w = 1/3, sqrW = sqrt(w);//weight is + + var a00 = cosR + (1.0 - cosR) * w; + var a01 = w * (1.0 - cosR) - sqrW * sinR; + var a02 = w * (1.0 - cosR) + sqrW * sinR; + + var a10 = w * (1.0 - cosR) + sqrW * sinR; + var a11 = cosR + w*(1.0 - cosR); + var a12 = w * (1.0 - cosR) - sqrW * sinR; + + var a20 = w * (1.0 - cosR) - sqrW * sinR; + var a21 = w * (1.0 - cosR) + sqrW * sinR; + var a22 = cosR + w * (1.0 - cosR); + + + var matrix = [ + a00, a01, a02, 0, 0, + a10, a11, a12, 0, 0, + a20, a21, a22, 0, 0, + 0, 0, 0, 1, 0, + ]; + + this._loadMatrix(matrix, multiply); + } + + + /** + * Set the contrast matrix, increase the separation between dark and bright + * Increase contrast : shadows darker and highlights brighter + * Decrease contrast : bring the shadows up and the highlights down + * + * @param amount {number} value of the contrast + * @param multiply {boolean} refer to ._loadMatrix() method + */ + contrast(amount, multiply) + { + var v = (amount || 0) + 1; + var o = -128 * (v - 1); + + var matrix = [ + v, 0, 0, 0, o, + 0, v, 0, 0, o, + 0, 0, v, 0, o, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Set the saturation matrix, increase the separation between colors + * Increase saturation : increase contrast, brightness, and sharpness + * + * @param [amount=0] {number} + * @param [multiply] {boolean} refer to ._loadMatrix() method + */ + saturate(amount, multiply) + { + var x = (amount || 0) * 2 / 3 + 1; + var y = ((x - 1) * -0.5); + + var matrix = [ + x, y, y, 0, 0, + y, x, y, 0, 0, + y, y, x, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Desaturate image (remove color) + * + * Call the saturate function + * + */ + desaturate() // jshint unused:false + { + this.saturate(-1); + } + + /** + * Negative image (inverse of classic rgb matrix) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + negative(multiply) + { + var matrix = [ + 0, 1, 1, 0, 0, + 1, 0, 1, 0, 0, + 1, 1, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Sepia image + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + sepia(multiply) + { + var matrix = [ + 0.393, 0.7689999, 0.18899999, 0, 0, + 0.349, 0.6859999, 0.16799999, 0, 0, + 0.272, 0.5339999, 0.13099999, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color motion picture process invented in 1916 (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + technicolor(multiply) + { + var matrix = [ + 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, + -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, + -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Polaroid filter + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + polaroid(multiply) + { + var matrix = [ + 1.438, -0.062, -0.062, 0, 0, + -0.122, 1.378, -0.122, 0, 0, + -0.016, -0.016, 1.483, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Filter who transforms : Red -> Blue and Blue -> Red + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + toBGR(multiply) + { + var matrix = [ + 0, 0, 1, 0, 0, + 0, 1, 0, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + kodachrome(multiply) + { + var matrix = [ + 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, + -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, + -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /** + * Brown delicious browni filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + browni(multiply) + { + var matrix = [ + 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, + -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, + 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Vintage filter (thanks Dominic Szablewski) + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + vintage(multiply) + { + var matrix = [ + 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, + 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, + 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * We don't know exactly what it does, kind of gradient map, but funny to play with! + * + * @param desaturation {number} + * @param toned {number} + * @param lightColor {string} (example : "0xFFE580") + * @param darkColor {string} (example : "0xFFE580") + * + * @param multiply {boolean} refer to ._loadMatrix() method + */ + colorTone(desaturation, toned, lightColor, darkColor, multiply) + { + desaturation = desaturation || 0.2; + toned = toned || 0.15; + lightColor = lightColor || 0xFFE580; + darkColor = darkColor || 0x338000; + + var lR = ((lightColor >> 16) & 0xFF) / 255; + var lG = ((lightColor >> 8) & 0xFF) / 255; + var lB = (lightColor & 0xFF) / 255; + + var dR = ((darkColor >> 16) & 0xFF) / 255; + var dG = ((darkColor >> 8) & 0xFF) / 255; + var dB = (darkColor & 0xFF) / 255; + + var matrix = [ + 0.3, 0.59, 0.11, 0, 0, + lR, lG, lB, desaturation, 0, + dR, dG, dB, toned, 0, + lR - dR, lG - dG, lB - dB, 0, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Night effect + * + * @param intensity {number} + * @param multiply {boolean} refer to ._loadMatrix() method + */ + night(intensity, multiply) + { + intensity = intensity || 0.1; + var matrix = [ + intensity * ( -2.0), -intensity, 0, 0, 0, + -intensity, 0, intensity, 0, 0, + 0, intensity, intensity * 2.0, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + + /* + * Predator effect + * + * Erase the current matrix by setting a new indepent one + * + * @param amount {number} how much the predator feels his future victim + * @param multiply {boolean} refer to ._loadMatrix() method + */ + predator(amount, multiply) + { + var matrix = [ + 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, + -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, + -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * LSD effect + * + * Multiply the current matrix + * + * @param amount {number} How crazy is your effect + * @param multiply {boolean} refer to ._loadMatrix() method + */ + lsd(multiply) + { + var matrix = [ + 2, -0.4, 0.5, 0, 0, + -0.5, 2, -0.4, 0, 0, + -0.4, -0.5, 3, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, multiply); + } + + /* + * Erase the current matrix by setting the default one + * + */ + reset() + { + var matrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + + this._loadMatrix(matrix, false); + } + } -ColorMatrixFilter.prototype = Object.create(core.Filter.prototype); -ColorMatrixFilter.prototype.constructor = ColorMatrixFilter; -module.exports = ColorMatrixFilter; - - -/** - * Transforms current matrix and set the new one - * - * @param matrix {number[]} (mat 5x4) - * @param multiply {boolean} if true, current matrix and matrix are multiplied. If false, just set the current matrix with @param matrix - */ -ColorMatrixFilter.prototype._loadMatrix = function (matrix, multiply) -{ - multiply = !!multiply; - - var newMatrix = matrix; - - if (multiply) { - this._multiply(newMatrix, this.uniforms.m, matrix); - newMatrix = this._colorMatrix(newMatrix); - } - - // set the new matrix - this.uniforms.m = newMatrix; -}; - -/** - * Multiplies two mat5's - * - * @param out {number[]} (mat 5x4) the receiving matrix - * @param a {number[]} (mat 5x4) the first operand - * @param b {number[]} (mat 5x4) the second operand - * @returns out {number[]} (mat 5x4) - */ -ColorMatrixFilter.prototype._multiply = function (out, a, b) -{ - - // Red Channel - out[0] = (a[0] * b[0]) + (a[1] * b[5]) + (a[2] * b[10]) + (a[3] * b[15]); - out[1] = (a[0] * b[1]) + (a[1] * b[6]) + (a[2] * b[11]) + (a[3] * b[16]); - out[2] = (a[0] * b[2]) + (a[1] * b[7]) + (a[2] * b[12]) + (a[3] * b[17]); - out[3] = (a[0] * b[3]) + (a[1] * b[8]) + (a[2] * b[13]) + (a[3] * b[18]); - out[4] = (a[0] * b[4]) + (a[1] * b[9]) + (a[2] * b[14]) + (a[3] * b[19]); - - // Green Channel - out[5] = (a[5] * b[0]) + (a[6] * b[5]) + (a[7] * b[10]) + (a[8] * b[15]); - out[6] = (a[5] * b[1]) + (a[6] * b[6]) + (a[7] * b[11]) + (a[8] * b[16]); - out[7] = (a[5] * b[2]) + (a[6] * b[7]) + (a[7] * b[12]) + (a[8] * b[17]); - out[8] = (a[5] * b[3]) + (a[6] * b[8]) + (a[7] * b[13]) + (a[8] * b[18]); - out[9] = (a[5] * b[4]) + (a[6] * b[9]) + (a[7] * b[14]) + (a[8] * b[19]); - - // Blue Channel - out[10] = (a[10] * b[0]) + (a[11] * b[5]) + (a[12] * b[10]) + (a[13] * b[15]); - out[11] = (a[10] * b[1]) + (a[11] * b[6]) + (a[12] * b[11]) + (a[13] * b[16]); - out[12] = (a[10] * b[2]) + (a[11] * b[7]) + (a[12] * b[12]) + (a[13] * b[17]); - out[13] = (a[10] * b[3]) + (a[11] * b[8]) + (a[12] * b[13]) + (a[13] * b[18]); - out[14] = (a[10] * b[4]) + (a[11] * b[9]) + (a[12] * b[14]) + (a[13] * b[19]); - - // Alpha Channel - out[15] = (a[15] * b[0]) + (a[16] * b[5]) + (a[17] * b[10]) + (a[18] * b[15]); - out[16] = (a[15] * b[1]) + (a[16] * b[6]) + (a[17] * b[11]) + (a[18] * b[16]); - out[17] = (a[15] * b[2]) + (a[16] * b[7]) + (a[17] * b[12]) + (a[18] * b[17]); - out[18] = (a[15] * b[3]) + (a[16] * b[8]) + (a[17] * b[13]) + (a[18] * b[18]); - out[19] = (a[15] * b[4]) + (a[16] * b[9]) + (a[17] * b[14]) + (a[18] * b[19]); - - return out; -}; - -/** - * Create a Float32 Array and normalize the offset component to 0-1 - * - * @param matrix {number[]} (mat 5x4) - * @return m {number[]} (mat 5x4) with all values between 0-1 - */ -ColorMatrixFilter.prototype._colorMatrix = function (matrix) -{ - // Create a Float32 Array and normalize the offset component to 0-1 - var m = new Float32Array(matrix); - m[4] /= 255; - m[9] /= 255; - m[14] /= 255; - m[19] /= 255; - - return m; -}; - -/** - * Adjusts brightness - * - * @param b {number} value of the brigthness (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.brightness = function (b, multiply) -{ - var matrix = [ - b, 0, 0, 0, 0, - 0, b, 0, 0, 0, - 0, 0, b, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the matrices in grey scales - * - * @param scale {number} value of the grey (0 is black) - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.greyscale = function (scale, multiply) -{ - var matrix = [ - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - scale, scale, scale, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; //Americanized alias ColorMatrixFilter.prototype.grayscale = ColorMatrixFilter.prototype.greyscale; -/** - * Set the black and white matrice - * Multiply the current matrix - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.blackAndWhite = function (multiply) -{ - var matrix = [ - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0.3, 0.6, 0.1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the hue property of the color - * - * @param rotation {number} in degrees - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.hue = function (rotation, multiply) -{ - rotation = (rotation || 0) / 180 * Math.PI; - - var cosR = Math.cos(rotation), - sinR = Math.sin(rotation), - sqrt = Math.sqrt; - - /*a good approximation for hue rotation - This matrix is far better than the versions with magic luminance constants - formerly used here, but also used in the starling framework (flash) and known from this - old part of the internet: quasimondo.com/archives/000565.php - - This new matrix is based on rgb cube rotation in space. Look here for a more descriptive - implementation as a shader not a general matrix: - https://github.com/evanw/glfx.js/blob/58841c23919bd59787effc0333a4897b43835412/src/filters/adjust/huesaturation.js - - This is the source for the code: - see http://stackoverflow.com/questions/8507885/shift-hue-of-an-rgb-color/8510751#8510751 - */ - - var w = 1/3, sqrW = sqrt(w);//weight is - - var a00 = cosR + (1.0 - cosR) * w; - var a01 = w * (1.0 - cosR) - sqrW * sinR; - var a02 = w * (1.0 - cosR) + sqrW * sinR; - - var a10 = w * (1.0 - cosR) + sqrW * sinR; - var a11 = cosR + w*(1.0 - cosR); - var a12 = w * (1.0 - cosR) - sqrW * sinR; - - var a20 = w * (1.0 - cosR) - sqrW * sinR; - var a21 = w * (1.0 - cosR) + sqrW * sinR; - var a22 = cosR + w * (1.0 - cosR); - - - var matrix = [ - a00, a01, a02, 0, 0, - a10, a11, a12, 0, 0, - a20, a21, a22, 0, 0, - 0, 0, 0, 1, 0, - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/** - * Set the contrast matrix, increase the separation between dark and bright - * Increase contrast : shadows darker and highlights brighter - * Decrease contrast : bring the shadows up and the highlights down - * - * @param amount {number} value of the contrast - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.contrast = function (amount, multiply) -{ - var v = (amount || 0) + 1; - var o = -128 * (v - 1); - - var matrix = [ - v, 0, 0, 0, o, - 0, v, 0, 0, o, - 0, 0, v, 0, o, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Set the saturation matrix, increase the separation between colors - * Increase saturation : increase contrast, brightness, and sharpness - * - * @param [amount=0] {number} - * @param [multiply] {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.saturate = function (amount, multiply) -{ - var x = (amount || 0) * 2 / 3 + 1; - var y = ((x - 1) * -0.5); - - var matrix = [ - x, y, y, 0, 0, - y, x, y, 0, 0, - y, y, x, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Desaturate image (remove color) - * - * Call the saturate function - * - */ -ColorMatrixFilter.prototype.desaturate = function () // jshint unused:false -{ - this.saturate(-1); -}; - -/** - * Negative image (inverse of classic rgb matrix) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.negative = function (multiply) -{ - var matrix = [ - 0, 1, 1, 0, 0, - 1, 0, 1, 0, 0, - 1, 1, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Sepia image - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.sepia = function (multiply) -{ - var matrix = [ - 0.393, 0.7689999, 0.18899999, 0, 0, - 0.349, 0.6859999, 0.16799999, 0, 0, - 0.272, 0.5339999, 0.13099999, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color motion picture process invented in 1916 (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.technicolor = function (multiply) -{ - var matrix = [ - 1.9125277891456083, -0.8545344976951645, -0.09155508482755585, 0, 11.793603434377337, - -0.3087833385928097, 1.7658908555458428, -0.10601743074722245, 0, -70.35205161461398, - -0.231103377548616, -0.7501899197440212, 1.847597816108189, 0, 30.950940869491138, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Polaroid filter - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.polaroid = function (multiply) -{ - var matrix = [ - 1.438, -0.062, -0.062, 0, 0, - -0.122, 1.378, -0.122, 0, 0, - -0.016, -0.016, 1.483, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Filter who transforms : Red -> Blue and Blue -> Red - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.toBGR = function (multiply) -{ - var matrix = [ - 0, 0, 1, 0, 0, - 0, 1, 0, 0, 0, - 1, 0, 0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Color reversal film introduced by Eastman Kodak in 1935. (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.kodachrome = function (multiply) -{ - var matrix = [ - 1.1285582396593525, -0.3967382283601348, -0.03992559172921793, 0, 63.72958762196502, - -0.16404339962244616, 1.0835251566291304, -0.05498805115633132, 0, 24.732407896706203, - -0.16786010706155763, -0.5603416277695248, 1.6014850761964943, 0, 35.62982807460946, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/** - * Brown delicious browni filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.browni = function (multiply) -{ - var matrix = [ - 0.5997023498159715, 0.34553243048391263, -0.2708298674538042, 0, 47.43192855600873, - -0.037703249837783157, 0.8609577587992641, 0.15059552388459913, 0, -36.96841498319127, - 0.24113635128153335, -0.07441037908422492, 0.44972182064877153, 0, -7.562075277591283, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Vintage filter (thanks Dominic Szablewski) - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.vintage = function (multiply) -{ - var matrix = [ - 0.6279345635605994, 0.3202183420819367, -0.03965408211312453, 0, 9.651285835294123, - 0.02578397704808868, 0.6441188644374771, 0.03259127616149294, 0, 7.462829176470591, - 0.0466055556782719, -0.0851232987247891, 0.5241648018700465, 0, 5.159190588235296, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * We don't know exactly what it does, kind of gradient map, but funny to play with! - * - * @param desaturation {number} - * @param toned {number} - * @param lightColor {string} (example : "0xFFE580") - * @param darkColor {string} (example : "0xFFE580") - * - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.colorTone = function (desaturation, toned, lightColor, darkColor, multiply) -{ - desaturation = desaturation || 0.2; - toned = toned || 0.15; - lightColor = lightColor || 0xFFE580; - darkColor = darkColor || 0x338000; - - var lR = ((lightColor >> 16) & 0xFF) / 255; - var lG = ((lightColor >> 8) & 0xFF) / 255; - var lB = (lightColor & 0xFF) / 255; - - var dR = ((darkColor >> 16) & 0xFF) / 255; - var dG = ((darkColor >> 8) & 0xFF) / 255; - var dB = (darkColor & 0xFF) / 255; - - var matrix = [ - 0.3, 0.59, 0.11, 0, 0, - lR, lG, lB, desaturation, 0, - dR, dG, dB, toned, 0, - lR - dR, lG - dG, lB - dB, 0, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Night effect - * - * @param intensity {number} - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.night = function (intensity, multiply) -{ - intensity = intensity || 0.1; - var matrix = [ - intensity * ( -2.0), -intensity, 0, 0, 0, - -intensity, 0, intensity, 0, 0, - 0, intensity, intensity * 2.0, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - - -/* - * Predator effect - * - * Erase the current matrix by setting a new indepent one - * - * @param amount {number} how much the predator feels his future victim - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.predator = function (amount, multiply) -{ - var matrix = [ - 11.224130630493164 * amount, -4.794486999511719 * amount, -2.8746118545532227 * amount, 0 * amount, 0.40342438220977783 * amount, - -3.6330697536468506 * amount, 9.193157196044922 * amount, -2.951810836791992 * amount, 0 * amount, -1.316135048866272 * amount, - -3.2184197902679443 * amount, -4.2375030517578125 * amount, 7.476448059082031 * amount, 0 * amount, 0.8044459223747253 * amount, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * LSD effect - * - * Multiply the current matrix - * - * @param amount {number} How crazy is your effect - * @param multiply {boolean} refer to ._loadMatrix() method - */ -ColorMatrixFilter.prototype.lsd = function (multiply) -{ - var matrix = [ - 2, -0.4, 0.5, 0, 0, - -0.5, 2, -0.4, 0, 0, - -0.4, -0.5, 3, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, multiply); -}; - -/* - * Erase the current matrix by setting the default one - * - */ -ColorMatrixFilter.prototype.reset = function () -{ - var matrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - - this._loadMatrix(matrix, false); -}; - +module.exports = ColorMatrixFilter; Object.defineProperties(ColorMatrixFilter.prototype, { /** diff --git a/src/filters/displacement/DisplacementFilter.js b/src/filters/displacement/DisplacementFilter.js index 32276a1..026487a 100644 --- a/src/filters/displacement/DisplacementFilter.js +++ b/src/filters/displacement/DisplacementFilter.js @@ -12,52 +12,52 @@ * @param sprite {PIXI.Sprite} The sprite used for the displacement map. (make sure its added to the scene!) * @param scale {number} The scale of the displacement */ -function DisplacementFilter(sprite, scale) -{ - var maskMatrix = new core.Matrix(); - sprite.renderable = false; - - core.Filter.call(this, - // vertex shader -// glslify('./displacement.vert'), - glslify('../fragments/default-filter-matrix.vert'), - // fragment shader - glslify('./displacement.frag') - - ); - - this.maskSprite = sprite; - this.maskMatrix = maskMatrix; - - this.uniforms.mapSampler = sprite.texture; - this.uniforms.filterMatrix = maskMatrix.toArray(true); - this.uniforms.scale = { x: 1, y: 1 }; - - if (scale === null || scale === undefined) +class DisplacementFilter extends core.Filter { + constructor(sprite, scale) { - scale = 20; + var maskMatrix = new core.Matrix(); + sprite.renderable = false; + + super( + // vertex shader + // glslify('./displacement.vert'), + glslify('../fragments/default-filter-matrix.vert'), + // fragment shader + glslify('./displacement.frag') + + ); + + this.maskSprite = sprite; + this.maskMatrix = maskMatrix; + + this.uniforms.mapSampler = sprite.texture; + this.uniforms.filterMatrix = maskMatrix.toArray(true); + this.uniforms.scale = { x: 1, y: 1 }; + + if (scale === null || scale === undefined) + { + scale = 20; + } + + this.scale = new core.Point(scale, scale); } - this.scale = new core.Point(scale, scale); + apply(filterManager, input, output) + { + var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); + + this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); + this.uniforms.scale.x = this.scale.x * ratio; + this.uniforms.scale.y = this.scale.y * ratio; + + // draw the filter... + filterManager.applyFilter(this, input, output); + } + } -DisplacementFilter.prototype = Object.create(core.Filter.prototype); -DisplacementFilter.prototype.constructor = DisplacementFilter; module.exports = DisplacementFilter; -DisplacementFilter.prototype.apply = function (filterManager, input, output) -{ - var ratio = (1/output.destinationFrame.width) * (output.size.width/input.size.width); /// // * 2 //4//this.strength / 4 / this.passes * (input.frame.width / input.size.width); - - this.uniforms.filterMatrix = filterManager.calculateSpriteMatrix(this.maskMatrix, this.maskSprite); - this.uniforms.scale.x = this.scale.x * ratio; - this.uniforms.scale.y = this.scale.y * ratio; - - // draw the filter... - filterManager.applyFilter(this, input, output); -}; - - Object.defineProperties(DisplacementFilter.prototype, { /** * The texture used for the displacement map. Must be power of 2 sized texture. diff --git a/src/filters/fxaa/FXAAFilter.js b/src/filters/fxaa/FXAAFilter.js index 9b5e8b4..dd2a6a2 100644 --- a/src/filters/fxaa/FXAAFilter.js +++ b/src/filters/fxaa/FXAAFilter.js @@ -14,20 +14,18 @@ * @memberof PIXI * */ -function FXAAFilter() -{ - //TODO - needs work - core.Filter.call(this, +class FXAAFilter extends core.Filter { + constructor() + { + //TODO - needs work + super( + // vertex shader + glslify('./fxaa.vert'), + // fragment shader + glslify('./fxaa.frag') + ); - // vertex shader - glslify('./fxaa.vert'), - // fragment shader - glslify('./fxaa.frag') - ); - + } } -FXAAFilter.prototype = Object.create(core.Filter.prototype); -FXAAFilter.prototype.constructor = FXAAFilter; - module.exports = FXAAFilter; diff --git a/src/filters/noise/NoiseFilter.js b/src/filters/noise/NoiseFilter.js index f333659..29ac4d0 100644 --- a/src/filters/noise/NoiseFilter.js +++ b/src/filters/noise/NoiseFilter.js @@ -13,20 +13,20 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function NoiseFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./noise.frag') - ); +class NoiseFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./noise.frag') + ); - this.noise = 0.5; + this.noise = 0.5; + } } -NoiseFilter.prototype = Object.create(core.Filter.prototype); -NoiseFilter.prototype.constructor = NoiseFilter; module.exports = NoiseFilter; Object.defineProperties(NoiseFilter.prototype, { diff --git a/src/filters/void/VoidFilter.js b/src/filters/void/VoidFilter.js index 786c852..b791d25 100644 --- a/src/filters/void/VoidFilter.js +++ b/src/filters/void/VoidFilter.js @@ -9,18 +9,18 @@ * @extends PIXI.Filter * @memberof PIXI.filters */ -function VoidFilter() -{ - core.Filter.call(this, - // vertex shader - glslify('../fragments/default.vert'), - // fragment shader - glslify('./void.frag') - ); +class VoidFilter extends core.Filter { + constructor() + { + super( + // vertex shader + glslify('../fragments/default.vert'), + // fragment shader + glslify('./void.frag') + ); - this.glShaderKey = 'void'; + this.glShaderKey = 'void'; + } } -VoidFilter.prototype = Object.create(core.Filter.prototype); -VoidFilter.prototype.constructor = VoidFilter; module.exports = VoidFilter; diff --git a/src/interaction/InteractionData.js b/src/interaction/InteractionData.js index f058e3d..ed89fdd 100644 --- a/src/interaction/InteractionData.js +++ b/src/interaction/InteractionData.js @@ -6,42 +6,44 @@ * @class * @memberof PIXI.interaction */ -function InteractionData() -{ - /** - * This point stores the global coords of where the touch/mouse event happened - * - * @member {PIXI.Point} - */ - this.global = new core.Point(); +class InteractionData { + constructor() + { + /** + * This point stores the global coords of where the touch/mouse event happened + * + * @member {PIXI.Point} + */ + this.global = new core.Point(); + + /** + * The target Sprite that was interacted with + * + * @member {PIXI.Sprite} + */ + this.target = null; + + /** + * When passed to an event handler, this will be the original DOM Event that was captured + * + * @member {Event} + */ + this.originalEvent = null; + } /** - * The target Sprite that was interacted with + * This will return the local coordinates of the specified displayObject for this InteractionData * - * @member {PIXI.Sprite} + * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off + * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) + * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) + * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject */ - this.target = null; + getLocalPosition(displayObject, point, globalPos) + { + return displayObject.worldTransform.applyInverse(globalPos || this.global, point); + } - /** - * When passed to an event handler, this will be the original DOM Event that was captured - * - * @member {Event} - */ - this.originalEvent = null; } -InteractionData.prototype.constructor = InteractionData; module.exports = InteractionData; - -/** - * This will return the local coordinates of the specified displayObject for this InteractionData - * - * @param displayObject {PIXI.DisplayObject} The DisplayObject that you would like the local coords off - * @param [point] {PIXI.Point} A Point object in which to store the value, optional (otherwise will create a new point) - * @param [globalPos] {PIXI.Point} A Point object containing your custom global coords, optional (otherwise will use the current global coords) - * @return {PIXI.Point} A point containing the coordinates of the InteractionData position relative to the DisplayObject - */ -InteractionData.prototype.getLocalPosition = function (displayObject, point, globalPos) -{ - return displayObject.worldTransform.applyInverse(globalPos || this.global, point); -}; diff --git a/src/interaction/InteractionManager.js b/src/interaction/InteractionManager.js index 465523d..b922604 100644 --- a/src/interaction/InteractionManager.js +++ b/src/interaction/InteractionManager.js @@ -21,1092 +21,1093 @@ * @param [options.autoPreventDefault=true] {boolean} Should the manager automatically prevent default browser actions. * @param [options.interactionFrequency=10] {number} Frequency increases the interaction events will be checked. */ -function InteractionManager(renderer, options) -{ - EventEmitter.call(this); - - options = options || {}; - - /** - * The renderer this interaction manager works for. - * - * @member {PIXI.SystemRenderer} - */ - this.renderer = renderer; - - /** - * Should default browser actions automatically be prevented. - * - * @member {boolean} - * @default true - */ - this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; - - /** - * As this frequency increases the interaction events will be checked more often. - * - * @member {number} - * @default 10 - */ - this.interactionFrequency = options.interactionFrequency || 10; - - /** - * The mouse data - * - * @member {PIXI.interaction.InteractionData} - */ - this.mouse = new InteractionData(); - - // setting the pointer to start off far off screen will mean that mouse over does - // not get called before we even move the mouse. - this.mouse.global.set(-999999); - - /** - * An event data object to handle all the event tracking/dispatching - * - * @member {object} - */ - this.eventData = { - stopped: false, - target: null, - type: null, - data: this.mouse, - stopPropagation:function(){ - this.stopped = true; - } - }; - - /** - * Tiny little interactiveData pool ! - * - * @member {PIXI.interaction.InteractionData[]} - */ - this.interactiveDataPool = []; - - /** - * The DOM element to bind to. - * - * @member {HTMLElement} - * @private - */ - this.interactionDOMElement = null; - - /** - * This property determins if mousemove and touchmove events are fired only when the cursror is over the object - * Setting to true will make things work more in line with how the DOM verison works. - * Setting to false can make things easier for things like dragging - * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. - * @member {boolean} - * @private - */ - this.moveWhenInside = false; - - /** - * Have events been attached to the dom element? - * - * @member {boolean} - * @private - */ - this.eventsAdded = false; - - //this will make it so that you don't have to call bind all the time - - /** - * @member {Function} - * @private - */ - this.onMouseUp = this.onMouseUp.bind(this); - this.processMouseUp = this.processMouseUp.bind( this ); - - - /** - * @member {Function} - * @private - */ - this.onMouseDown = this.onMouseDown.bind(this); - this.processMouseDown = this.processMouseDown.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseMove = this.onMouseMove.bind( this ); - this.processMouseMove = this.processMouseMove.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOut = this.onMouseOut.bind(this); - this.processMouseOverOut = this.processMouseOverOut.bind( this ); - - /** - * @member {Function} - * @private - */ - this.onMouseOver = this.onMouseOver.bind(this); - - - /** - * @member {Function} - * @private - */ - this.onTouchStart = this.onTouchStart.bind(this); - this.processTouchStart = this.processTouchStart.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchEnd = this.onTouchEnd.bind(this); - this.processTouchEnd = this.processTouchEnd.bind(this); - - /** - * @member {Function} - * @private - */ - this.onTouchMove = this.onTouchMove.bind(this); - this.processTouchMove = this.processTouchMove.bind(this); - - /** - * Every update cursor will be reset to this value, if some element wont override it in its hitTest - * @member {string} - * @default 'inherit' - */ - this.defaultCursorStyle = 'inherit'; - - /** - * The css style of the cursor that is being used - * @member {string} - */ - this.currentCursorStyle = 'inherit'; - - /** - * Internal cached var - * @member {PIXI.Point} - * @private - */ - this._tempPoint = new core.Point(); - - - /** - * The current resolution / device pixel ratio. - * @member {number} - * @default 1 - */ - this.resolution = 1; - - this.setTargetElement(this.renderer.view, this.renderer.resolution); - - /** - * Fired when a pointing device button (usually a mouse button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mousedown - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightdown - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event mouseup - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. - * - * @memberof PIXI.interaction.InteractionManager# - * @event rightup - */ - - /** - * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. - * - * @event click - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. - * - * @event rightclick - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. - * - * @event mouseupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially - * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. - * - * @event rightupoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved while over the display object - * - * @event mousemove - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved onto the display object - * - * @event mouseover - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a pointing device (usually a mouse) is moved off the display object - * - * @event mouseout - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed on the display object. - * - * @event touchstart - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed from the display object. - * - * @event touchend - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is placed and removed from the display object. - * - * @event tap - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. - * - * @event touchendoutside - * @memberof PIXI.interaction.InteractionManager# - */ - - /** - * Fired when a touch point is moved along the display object. - * - * @event touchmove - * @memberof PIXI.interaction.InteractionManager# - */ -} - -InteractionManager.prototype = Object.create(EventEmitter.prototype); -InteractionManager.prototype.constructor = InteractionManager; -module.exports = InteractionManager; - -/** - * Sets the DOM element which will receive mouse/touch events. This is useful for when you have - * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate - * another DOM element to receive those events. - * - * @param element {HTMLElement} the DOM element which will receive mouse and touch events. - * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). - * @private - */ -InteractionManager.prototype.setTargetElement = function (element, resolution) -{ - this.removeEvents(); - - this.interactionDOMElement = element; - - this.resolution = resolution || 1; - - this.addEvents(); -}; - -/** - * Registers all the DOM events - * - * @private - */ -InteractionManager.prototype.addEvents = function () -{ - if (!this.interactionDOMElement) +class InteractionManager extends EventEmitter { + constructor(renderer, options) { - return; + super(); + + options = options || {}; + + /** + * The renderer this interaction manager works for. + * + * @member {PIXI.SystemRenderer} + */ + this.renderer = renderer; + + /** + * Should default browser actions automatically be prevented. + * + * @member {boolean} + * @default true + */ + this.autoPreventDefault = options.autoPreventDefault !== undefined ? options.autoPreventDefault : true; + + /** + * As this frequency increases the interaction events will be checked more often. + * + * @member {number} + * @default 10 + */ + this.interactionFrequency = options.interactionFrequency || 10; + + /** + * The mouse data + * + * @member {PIXI.interaction.InteractionData} + */ + this.mouse = new InteractionData(); + + // setting the pointer to start off far off screen will mean that mouse over does + // not get called before we even move the mouse. + this.mouse.global.set(-999999); + + /** + * An event data object to handle all the event tracking/dispatching + * + * @member {object} + */ + this.eventData = { + stopped: false, + target: null, + type: null, + data: this.mouse, + stopPropagation:function(){ + this.stopped = true; + } + }; + + /** + * Tiny little interactiveData pool ! + * + * @member {PIXI.interaction.InteractionData[]} + */ + this.interactiveDataPool = []; + + /** + * The DOM element to bind to. + * + * @member {HTMLElement} + * @private + */ + this.interactionDOMElement = null; + + /** + * This property determins if mousemove and touchmove events are fired only when the cursror is over the object + * Setting to true will make things work more in line with how the DOM verison works. + * Setting to false can make things easier for things like dragging + * It is currently set to false as this is how pixi used to work. This will be set to true in future versions of pixi. + * @member {boolean} + * @private + */ + this.moveWhenInside = false; + + /** + * Have events been attached to the dom element? + * + * @member {boolean} + * @private + */ + this.eventsAdded = false; + + //this will make it so that you don't have to call bind all the time + + /** + * @member {Function} + * @private + */ + this.onMouseUp = this.onMouseUp.bind(this); + this.processMouseUp = this.processMouseUp.bind( this ); + + + /** + * @member {Function} + * @private + */ + this.onMouseDown = this.onMouseDown.bind(this); + this.processMouseDown = this.processMouseDown.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseMove = this.onMouseMove.bind( this ); + this.processMouseMove = this.processMouseMove.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOut = this.onMouseOut.bind(this); + this.processMouseOverOut = this.processMouseOverOut.bind( this ); + + /** + * @member {Function} + * @private + */ + this.onMouseOver = this.onMouseOver.bind(this); + + + /** + * @member {Function} + * @private + */ + this.onTouchStart = this.onTouchStart.bind(this); + this.processTouchStart = this.processTouchStart.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchEnd = this.onTouchEnd.bind(this); + this.processTouchEnd = this.processTouchEnd.bind(this); + + /** + * @member {Function} + * @private + */ + this.onTouchMove = this.onTouchMove.bind(this); + this.processTouchMove = this.processTouchMove.bind(this); + + /** + * Every update cursor will be reset to this value, if some element wont override it in its hitTest + * @member {string} + * @default 'inherit' + */ + this.defaultCursorStyle = 'inherit'; + + /** + * The css style of the cursor that is being used + * @member {string} + */ + this.currentCursorStyle = 'inherit'; + + /** + * Internal cached var + * @member {PIXI.Point} + * @private + */ + this._tempPoint = new core.Point(); + + + /** + * The current resolution / device pixel ratio. + * @member {number} + * @default 1 + */ + this.resolution = 1; + + this.setTargetElement(this.renderer.view, this.renderer.resolution); + + /** + * Fired when a pointing device button (usually a mouse button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mousedown + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed on the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightdown + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event mouseup + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released over the display object. + * + * @memberof PIXI.interaction.InteractionManager# + * @event rightup + */ + + /** + * Fired when a pointing device button (usually a mouse button) is pressed and released on the display object. + * + * @event click + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is pressed and released on the display object. + * + * @event rightclick + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device button (usually a mouse button) is released outside the display object that initially registered a [mousedown]{@link PIXI.interaction.InteractionManager#event:mousedown}. + * + * @event mouseupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device secondary button (usually a mouse right-button) is released outside the display object that initially + * registered a [rightdown]{@link PIXI.interaction.InteractionManager#event:rightdown}. + * + * @event rightupoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved while over the display object + * + * @event mousemove + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved onto the display object + * + * @event mouseover + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a pointing device (usually a mouse) is moved off the display object + * + * @event mouseout + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed on the display object. + * + * @event touchstart + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed from the display object. + * + * @event touchend + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is placed and removed from the display object. + * + * @event tap + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is removed outside of the display object that initially registered a [touchstart]{@link PIXI.interaction.InteractionManager#event:touchstart}. + * + * @event touchendoutside + * @memberof PIXI.interaction.InteractionManager# + */ + + /** + * Fired when a touch point is moved along the display object. + * + * @event touchmove + * @memberof PIXI.interaction.InteractionManager# + */ } - core.ticker.shared.add(this.update, this); - - if (window.navigator.msPointerEnabled) + /** + * Sets the DOM element which will receive mouse/touch events. This is useful for when you have + * other DOM elements on top of the renderers Canvas element. With this you'll be bale to deletegate + * another DOM element to receive those events. + * + * @param element {HTMLElement} the DOM element which will receive mouse and touch events. + * @param [resolution=1] {number} The resolution / device pixel ratio of the new element (relative to the canvas). + * @private + */ + setTargetElement(element, resolution) { - this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; - this.interactionDOMElement.style['-ms-touch-action'] = 'none'; + this.removeEvents(); + + this.interactionDOMElement = element; + + this.resolution = resolution || 1; + + this.addEvents(); } - window.document.addEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); - - window.addEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = true; -}; - -/** - * Removes all the DOM events that were previously registered - * - * @private - */ -InteractionManager.prototype.removeEvents = function () -{ - if (!this.interactionDOMElement) + /** + * Registers all the DOM events + * + * @private + */ + addEvents() { - return; - } - - core.ticker.shared.remove(this.update); - - if (window.navigator.msPointerEnabled) - { - this.interactionDOMElement.style['-ms-content-zooming'] = ''; - this.interactionDOMElement.style['-ms-touch-action'] = ''; - } - - window.document.removeEventListener('mousemove', this.onMouseMove, true); - this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); - this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); - this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); - - this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); - this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); - this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); - - this.interactionDOMElement = null; - - window.removeEventListener('mouseup', this.onMouseUp, true); - - this.eventsAdded = false; -}; - -/** - * Updates the state of interactive objects. - * Invoked by a throttled ticker update from - * {@link PIXI.ticker.shared}. - * - * @param deltaTime {number} time delta since last tick - */ -InteractionManager.prototype.update = function (deltaTime) -{ - this._deltaTime += deltaTime; - - if (this._deltaTime < this.interactionFrequency) - { - return; - } - - this._deltaTime = 0; - - if (!this.interactionDOMElement) - { - return; - } - - // if the user move the mouse this check has already been dfone using the mouse move! - if(this.didMove) - { - this.didMove = false; - return; - } - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO -}; - -/** - * Dispatches an event on the display object that was interacted with - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question - * @param eventString {string} the name of the event (e.g, mousedown) - * @param eventData {object} the event data object - * @private - */ -InteractionManager.prototype.dispatchEvent = function ( displayObject, eventString, eventData ) -{ - if(!eventData.stopped) - { - eventData.target = displayObject; - eventData.type = eventString; - - displayObject.emit( eventString, eventData ); - - if( displayObject[eventString] ) + if (!this.interactionDOMElement) { - displayObject[eventString]( eventData ); + return; } - } -}; -/** - * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. - * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. - * - * @param {PIXI.Point} point the point that the result will be stored in - * @param {number} x the x coord of the position to map - * @param {number} y the y coord of the position to map - */ -InteractionManager.prototype.mapPositionToPoint = function ( point, x, y ) -{ - var rect; - // IE 11 fix - if(!this.interactionDOMElement.parentElement) - { - rect = { x: 0, y: 0, width: 0, height: 0 }; - } else { - rect = this.interactionDOMElement.getBoundingClientRect(); - } + core.ticker.shared.add(this.update, this); - point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; - point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; -}; - -/** - * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. - * It will also take care of hit testing the interactive objects and passes the hit across in the function. - * - * @param point {PIXI.Point} the point that is tested for collision - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) - * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function - * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point - * @param [interactive] {boolean} Whether the displayObject is interactive - * @return {boolean} returns true if the displayObject hit the point - */ -InteractionManager.prototype.processInteractive = function (point, displayObject, func, hitTest, interactive) -{ - if(!displayObject || !displayObject.visible) - { - return false; - } - - // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ - // - // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. - // An object will be hit test if the following is true: - // - // 1: It is interactive. - // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. - // - // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests - // A final optimisation is that an object is not hit test directly if a child has already been hit. - - var hit = false, - interactiveParent = interactive = displayObject.interactive || interactive; - - - - - // if the displayobject has a hitArea, then it does not need to hitTest children. - if(displayObject.hitArea) - { - interactiveParent = false; - } - - // it has a mask! Then lets hit test that before continuing.. - if(hitTest && displayObject._mask) - { - if(!displayObject._mask.containsPoint(point)) + if (window.navigator.msPointerEnabled) { - hitTest = false; + this.interactionDOMElement.style['-ms-content-zooming'] = 'none'; + this.interactionDOMElement.style['-ms-touch-action'] = 'none'; } + + window.document.addEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.addEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.addEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.addEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.addEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.addEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.addEventListener('touchmove', this.onTouchMove, true); + + window.addEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = true; } - // it has a filterArea! Same as mask but easier, its a rectangle - if(hitTest && displayObject.filterArea) + /** + * Removes all the DOM events that were previously registered + * + * @private + */ + removeEvents() { - if(!displayObject.filterArea.contains(point.x, point.y)) + if (!this.interactionDOMElement) { - hitTest = false; + return; } + + core.ticker.shared.remove(this.update); + + if (window.navigator.msPointerEnabled) + { + this.interactionDOMElement.style['-ms-content-zooming'] = ''; + this.interactionDOMElement.style['-ms-touch-action'] = ''; + } + + window.document.removeEventListener('mousemove', this.onMouseMove, true); + this.interactionDOMElement.removeEventListener('mousedown', this.onMouseDown, true); + this.interactionDOMElement.removeEventListener('mouseout', this.onMouseOut, true); + this.interactionDOMElement.removeEventListener('mouseover', this.onMouseOver, true); + + this.interactionDOMElement.removeEventListener('touchstart', this.onTouchStart, true); + this.interactionDOMElement.removeEventListener('touchend', this.onTouchEnd, true); + this.interactionDOMElement.removeEventListener('touchmove', this.onTouchMove, true); + + this.interactionDOMElement = null; + + window.removeEventListener('mouseup', this.onMouseUp, true); + + this.eventsAdded = false; } - // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. - // This will allow pixi to completly ignore and bypass checking the displayObjects children. - if(displayObject.interactiveChildren) + /** + * Updates the state of interactive objects. + * Invoked by a throttled ticker update from + * {@link PIXI.ticker.shared}. + * + * @param deltaTime {number} time delta since last tick + */ + update(deltaTime) { - var children = displayObject.children; + this._deltaTime += deltaTime; - for (var i = children.length-1; i >= 0; i--) + if (this._deltaTime < this.interactionFrequency) { - var child = children[i]; + return; + } - // time to get recursive.. if this function will return if somthing is hit.. - if(this.processInteractive(point, child, func, hitTest, interactiveParent)) + this._deltaTime = 0; + + if (!this.interactionDOMElement) + { + return; + } + + // if the user move the mouse this check has already been dfone using the mouse move! + if(this.didMove) + { + this.didMove = false; + return; + } + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, true ); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO + } + + /** + * Dispatches an event on the display object that was interacted with + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the display object in question + * @param eventString {string} the name of the event (e.g, mousedown) + * @param eventData {object} the event data object + * @private + */ + dispatchEvent( displayObject, eventString, eventData ) + { + if(!eventData.stopped) + { + eventData.target = displayObject; + eventData.type = eventString; + + displayObject.emit( eventString, eventData ); + + if( displayObject[eventString] ) { - // its a good idea to check if a child has lost its parent. - // this means it has been removed whilst looping so its best - if(!child.parent) + displayObject[eventString]( eventData ); + } + } + } + + /** + * Maps x and y coords from a DOM object and maps them correctly to the pixi view. The resulting value is stored in the point. + * This takes into account the fact that the DOM element could be scaled and positioned anywhere on the screen. + * + * @param {PIXI.Point} point the point that the result will be stored in + * @param {number} x the x coord of the position to map + * @param {number} y the y coord of the position to map + */ + mapPositionToPoint( point, x, y ) + { + var rect; + // IE 11 fix + if(!this.interactionDOMElement.parentElement) + { + rect = { x: 0, y: 0, width: 0, height: 0 }; + } else { + rect = this.interactionDOMElement.getBoundingClientRect(); + } + + point.x = ( ( x - rect.left ) * (this.interactionDOMElement.width / rect.width ) ) / this.resolution; + point.y = ( ( y - rect.top ) * (this.interactionDOMElement.height / rect.height ) ) / this.resolution; + } + + /** + * This function is provides a neat way of crawling through the scene graph and running a specified function on all interactive objects it finds. + * It will also take care of hit testing the interactive objects and passes the hit across in the function. + * + * @param point {PIXI.Point} the point that is tested for collision + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} the displayObject that will be hit test (recurcsivly crawls its children) + * @param [func] {Function} the function that will be called on each interactive object. The displayObject and hit will be passed to the function + * @param [hitTest] {boolean} this indicates if the objects inside should be hit test against the point + * @param [interactive] {boolean} Whether the displayObject is interactive + * @return {boolean} returns true if the displayObject hit the point + */ + processInteractive(point, displayObject, func, hitTest, interactive) + { + if(!displayObject || !displayObject.visible) + { + return false; + } + + // Took a little while to rework this function correctly! But now it is done and nice and optimised. ^_^ + // + // This function will now loop through all objects and then only hit test the objects it HAS to, not all of them. MUCH faster.. + // An object will be hit test if the following is true: + // + // 1: It is interactive. + // 2: It belongs to a parent that is interactive AND one of the parents children have not already been hit. + // + // As another little optimisation once an interactive object has been hit we can carry on through the scenegraph, but we know that there will be no more hits! So we can avoid extra hit tests + // A final optimisation is that an object is not hit test directly if a child has already been hit. + + var hit = false, + interactiveParent = interactive = displayObject.interactive || interactive; + + + + + // if the displayobject has a hitArea, then it does not need to hitTest children. + if(displayObject.hitArea) + { + interactiveParent = false; + } + + // it has a mask! Then lets hit test that before continuing.. + if(hitTest && displayObject._mask) + { + if(!displayObject._mask.containsPoint(point)) + { + hitTest = false; + } + } + + // it has a filterArea! Same as mask but easier, its a rectangle + if(hitTest && displayObject.filterArea) + { + if(!displayObject.filterArea.contains(point.x, point.y)) + { + hitTest = false; + } + } + + // ** FREE TIP **! If an object is not interactive or has no buttons in it (such as a game scene!) set interactiveChildren to false for that displayObject. + // This will allow pixi to completly ignore and bypass checking the displayObjects children. + if(displayObject.interactiveChildren) + { + var children = displayObject.children; + + for (var i = children.length-1; i >= 0; i--) + { + var child = children[i]; + + // time to get recursive.. if this function will return if somthing is hit.. + if(this.processInteractive(point, child, func, hitTest, interactiveParent)) { - continue; + // its a good idea to check if a child has lost its parent. + // this means it has been removed whilst looping so its best + if(!child.parent) + { + continue; + } + + hit = true; + + // we no longer need to hit test any more objects in this container as we we now know the parent has been hit + interactiveParent = false; + + // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. + // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. + + //{ + hitTest = false; + //} + + // we can break now as we have hit an object. + } + } + } + + + + // no point running this if the item is not interactive or does not have an interactive parent. + if(interactive) + { + // if we are hit testing (as in we have no hit any objects yet) + // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! + if(hitTest && !hit) + { + + if(displayObject.hitArea) + { + displayObject.worldTransform.applyInverse(point, this._tempPoint); + hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + } + else if(displayObject.containsPoint) + { + hit = displayObject.containsPoint(point); } - hit = true; - // we no longer need to hit test any more objects in this container as we we now know the parent has been hit - interactiveParent = false; - - // If the child is interactive , that means that the object hit was actually interactive and not just the child of an interactive object. - // This means we no longer need to hit test anything else. We still need to run through all objects, but we don't need to perform any hit tests. - - //{ - hitTest = false; - //} - - // we can break now as we have hit an object. } - } - } - - - // no point running this if the item is not interactive or does not have an interactive parent. - if(interactive) - { - // if we are hit testing (as in we have no hit any objects yet) - // We also don't need to worry about hit testing if once of the displayObjects children has already been hit! - if(hitTest && !hit) - { - - if(displayObject.hitArea) + if(displayObject.interactive) { - displayObject.worldTransform.applyInverse(point, this._tempPoint); - hit = displayObject.hitArea.contains( this._tempPoint.x, this._tempPoint.y ); + func(displayObject, hit); } - else if(displayObject.containsPoint) + } + + return hit; + + } + + + /** + * Is called when the mouse button is pressed down on the renderer element + * + * @param event {Event} The DOM event of a mouse button being pressed down + * @private + */ + onMouseDown(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + if (this.autoPreventDefault) + { + this.mouse.originalEvent.preventDefault(); + } + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); + } + + /** + * Processes the result of the mouse down check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the dispay object + * @private + */ + processMouseDown( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + + if(hit) + { + displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; + this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); + } + } + + /** + * Is called when the mouse button is released on the renderer element + * + * @param event {Event} The DOM event of a mouse button being released + * @private + */ + onMouseUp(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); + + var isRightButton = event.button === 2 || event.which === 3; + this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); + } + + /** + * Processes the result of the mouse up check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseUp( displayObject, hit ) + { + var e = this.mouse.originalEvent; + + var isRightButton = e.button === 2 || e.which === 3; + var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; + + if(hit) + { + this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); + + if( displayObject[ isDown ] ) { - hit = displayObject.containsPoint(point); + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); + } + } + else + { + if( displayObject[ isDown ] ) + { + displayObject[ isDown ] = false; + this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); + } + } + } + + + /** + * Is called when the mouse moves across the renderer element + * + * @param event {Event} The DOM event of the mouse moving + * @private + */ + onMouseMove(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.didMove = true; + + this.cursor = this.defaultCursorStyle; + + this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); + + this.emit('mousemove', this.eventData); + + if (this.currentCursorStyle !== this.cursor) + { + this.currentCursorStyle = this.cursor; + this.interactionDOMElement.style.cursor = this.cursor; + } + + //TODO BUG for parents ineractive object (border order issue) + } + + /** + * Processes the result of the mouse move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseMove( displayObject, hit ) + { + this.processMouseOverOut(displayObject, hit); + + // only display on mouse over + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'mousemove', this.eventData); + } + } + + + /** + * Is called when the mouse is moved out of the renderer element + * + * @param event {Event} The DOM event of a mouse being moved out + * @private + */ + onMouseOut(event) + { + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; + this.eventData.stopped = false; + + // Update internal mouse reference + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); + + this.interactionDOMElement.style.cursor = this.defaultCursorStyle; + + // TODO optimize by not check EVERY TIME! maybe half as often? // + this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); + + this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); + + this.emit('mouseout', this.eventData); + } + + /** + * Processes the result of the mouse over/out check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processMouseOverOut( displayObject, hit ) + { + if(hit) + { + if(!displayObject._over) + { + displayObject._over = true; + this.dispatchEvent( displayObject, 'mouseover', this.eventData ); } - + if (displayObject.buttonMode) + { + this.cursor = displayObject.defaultCursor; + } } - - if(displayObject.interactive) + else { - func(displayObject, hit); + if(displayObject._over) + { + displayObject._over = false; + this.dispatchEvent( displayObject, 'mouseout', this.eventData); + } } } - return hit; - -}; - - -/** - * Is called when the mouse button is pressed down on the renderer element - * - * @param event {Event} The DOM event of a mouse button being pressed down - * @private - */ -InteractionManager.prototype.onMouseDown = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - if (this.autoPreventDefault) + /** + * Is called when the mouse enters the renderer element area + * + * @param event {Event} The DOM event of the mouse moving into the renderer view + * @private + */ + onMouseOver(event) { - this.mouse.originalEvent.preventDefault(); - } - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseDown, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightdown' : 'mousedown', this.eventData); -}; - -/** - * Processes the result of the mouse down check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the dispay object - * @private - */ -InteractionManager.prototype.processMouseDown = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - - if(hit) - { - displayObject[ isRightButton ? '_isRightDown' : '_isLeftDown' ] = true; - this.dispatchEvent( displayObject, isRightButton ? 'rightdown' : 'mousedown', this.eventData ); - } -}; - -/** - * Is called when the mouse button is released on the renderer element - * - * @param event {Event} The DOM event of a mouse button being released - * @private - */ -InteractionManager.prototype.onMouseUp = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseUp, true ); - - var isRightButton = event.button === 2 || event.which === 3; - this.emit(isRightButton ? 'rightup' : 'mouseup', this.eventData); -}; - -/** - * Processes the result of the mouse up check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseUp = function ( displayObject, hit ) -{ - var e = this.mouse.originalEvent; - - var isRightButton = e.button === 2 || e.which === 3; - var isDown = isRightButton ? '_isRightDown' : '_isLeftDown'; - - if(hit) - { - this.dispatchEvent( displayObject, isRightButton ? 'rightup' : 'mouseup', this.eventData ); - - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightclick' : 'click', this.eventData ); - } - } - else - { - if( displayObject[ isDown ] ) - { - displayObject[ isDown ] = false; - this.dispatchEvent( displayObject, isRightButton ? 'rightupoutside' : 'mouseupoutside', this.eventData ); - } - } -}; - - -/** - * Is called when the mouse moves across the renderer element - * - * @param event {Event} The DOM event of the mouse moving - * @private - */ -InteractionManager.prototype.onMouseMove = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.didMove = true; - - this.cursor = this.defaultCursorStyle; - - this.processInteractive(this.mouse.global, this.renderer._lastObjectRendered, this.processMouseMove, true ); - - this.emit('mousemove', this.eventData); - - if (this.currentCursorStyle !== this.cursor) - { - this.currentCursorStyle = this.cursor; - this.interactionDOMElement.style.cursor = this.cursor; - } - - //TODO BUG for parents ineractive object (border order issue) -}; - -/** - * Processes the result of the mouse move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseMove = function ( displayObject, hit ) -{ - this.processMouseOverOut(displayObject, hit); - - // only display on mouse over - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'mousemove', this.eventData); - } -}; - - -/** - * Is called when the mouse is moved out of the renderer element - * - * @param event {Event} The DOM event of a mouse being moved out - * @private - */ -InteractionManager.prototype.onMouseOut = function (event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - // Update internal mouse reference - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY); - - this.interactionDOMElement.style.cursor = this.defaultCursorStyle; - - // TODO optimize by not check EVERY TIME! maybe half as often? // - this.mapPositionToPoint( this.mouse.global, event.clientX, event.clientY ); - - this.processInteractive( this.mouse.global, this.renderer._lastObjectRendered, this.processMouseOverOut, false ); - - this.emit('mouseout', this.eventData); -}; - -/** - * Processes the result of the mouse over/out check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processMouseOverOut = function ( displayObject, hit ) -{ - if(hit) - { - if(!displayObject._over) - { - displayObject._over = true; - this.dispatchEvent( displayObject, 'mouseover', this.eventData ); - } - - if (displayObject.buttonMode) - { - this.cursor = displayObject.defaultCursor; - } - } - else - { - if(displayObject._over) - { - displayObject._over = false; - this.dispatchEvent( displayObject, 'mouseout', this.eventData); - } - } -}; - -/** - * Is called when the mouse enters the renderer element area - * - * @param event {Event} The DOM event of the mouse moving into the renderer view - * @private - */ -InteractionManager.prototype.onMouseOver = function(event) -{ - this.mouse.originalEvent = event; - this.eventData.data = this.mouse; - this.eventData.stopped = false; - - this.emit('mouseover', this.eventData); -}; - - -/** - * Is called when a touch is started on the renderer element - * - * @param event {Event} The DOM event of a touch starting on the renderer view - * @private - */ -InteractionManager.prototype.onTouchStart = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); - } - - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) - { - var touchEvent = changedTouches[i]; - //TODO POOL - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; + this.mouse.originalEvent = event; + this.eventData.data = this.mouse; this.eventData.stopped = false; - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); - - this.emit('touchstart', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchStart = function ( displayObject, hit ) -{ - if(hit) - { - displayObject._touchDown = true; - this.dispatchEvent( displayObject, 'touchstart', this.eventData ); - } -}; - - -/** - * Is called when a touch ends on the renderer element - * - * @param event {Event} The DOM event of a touch ending on the renderer view - * @private - */ -InteractionManager.prototype.onTouchEnd = function (event) -{ - if (this.autoPreventDefault) - { - event.preventDefault(); + this.emit('mouseover', this.eventData); } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - for (var i=0; i < cLength; i++) + /** + * Is called when a touch is started on the renderer element + * + * @param event {Event} The DOM event of a touch starting on the renderer view + * @private + */ + onTouchStart(event) { - var touchEvent = changedTouches[i]; - - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - //TODO this should be passed along.. no set - this.eventData.data = touchData; - this.eventData.stopped = false; - - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); - - this.emit('touchend', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of the end of a touch and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchEnd = function ( displayObject, hit ) -{ - if(hit) - { - this.dispatchEvent( displayObject, 'touchend', this.eventData ); - - if( displayObject._touchDown ) + if (this.autoPreventDefault) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'tap', this.eventData ); + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + //TODO POOL + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchStart, true ); + + this.emit('touchstart', this.eventData); + + this.returnTouchData( touchData ); } } - else + + /** + * Processes the result of a touch check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchStart( displayObject, hit ) { - if( displayObject._touchDown ) + if(hit) { - displayObject._touchDown = false; - this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + displayObject._touchDown = true; + this.dispatchEvent( displayObject, 'touchstart', this.eventData ); } } -}; -/** - * Is called when a touch is moved across the renderer element - * - * @param event {Event} The DOM event of a touch moving across the renderer view - * @private - */ -InteractionManager.prototype.onTouchMove = function (event) -{ - if (this.autoPreventDefault) + + /** + * Is called when a touch ends on the renderer element + * + * @param event {Event} The DOM event of a touch ending on the renderer view + * @private + */ + onTouchEnd(event) { - event.preventDefault(); + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + //TODO this should be passed along.. no set + this.eventData.data = touchData; + this.eventData.stopped = false; + + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchEnd, true ); + + this.emit('touchend', this.eventData); + + this.returnTouchData( touchData ); + } } - var changedTouches = event.changedTouches; - var cLength = changedTouches.length; - - for (var i=0; i < cLength; i++) + /** + * Processes the result of the end of a touch and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchEnd( displayObject, hit ) { - var touchEvent = changedTouches[i]; + if(hit) + { + this.dispatchEvent( displayObject, 'touchend', this.eventData ); - var touchData = this.getTouchData( touchEvent ); - - touchData.originalEvent = event; - - this.eventData.data = touchData; - this.eventData.stopped = false; - - this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); - - this.emit('touchmove', this.eventData); - - this.returnTouchData( touchData ); - } -}; - -/** - * Processes the result of a touch move check and dispatches the event if need be - * - * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested - * @param hit {boolean} the result of the hit test on the display object - * @private - */ -InteractionManager.prototype.processTouchMove = function ( displayObject, hit ) -{ - if(!this.moveWhenInside || hit) - { - this.dispatchEvent( displayObject, 'touchmove', this.eventData); - } -}; - -/** - * Grabs an interaction data object from the internal pool - * - * @param touchEvent {object} The touch event we need to pair with an interactionData object - * - * @private - */ -InteractionManager.prototype.getTouchData = function (touchEvent) -{ - var touchData = this.interactiveDataPool.pop(); - - if(!touchData) - { - touchData = new InteractionData(); + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'tap', this.eventData ); + } + } + else + { + if( displayObject._touchDown ) + { + displayObject._touchDown = false; + this.dispatchEvent( displayObject, 'touchendoutside', this.eventData ); + } + } } - touchData.identifier = touchEvent.identifier; - this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - - if(navigator.isCocoonJS) + /** + * Is called when a touch is moved across the renderer element + * + * @param event {Event} The DOM event of a touch moving across the renderer view + * @private + */ + onTouchMove(event) { - touchData.global.x = touchData.global.x / this.resolution; - touchData.global.y = touchData.global.y / this.resolution; + if (this.autoPreventDefault) + { + event.preventDefault(); + } + + var changedTouches = event.changedTouches; + var cLength = changedTouches.length; + + for (var i=0; i < cLength; i++) + { + var touchEvent = changedTouches[i]; + + var touchData = this.getTouchData( touchEvent ); + + touchData.originalEvent = event; + + this.eventData.data = touchData; + this.eventData.stopped = false; + + this.processInteractive( touchData.global, this.renderer._lastObjectRendered, this.processTouchMove, this.moveWhenInside ); + + this.emit('touchmove', this.eventData); + + this.returnTouchData( touchData ); + } } - touchEvent.globalX = touchData.global.x; - touchEvent.globalY = touchData.global.y; + /** + * Processes the result of a touch move check and dispatches the event if need be + * + * @param displayObject {PIXI.Container|PIXI.Sprite|PIXI.extras.TilingSprite} The display object that was tested + * @param hit {boolean} the result of the hit test on the display object + * @private + */ + processTouchMove( displayObject, hit ) + { + if(!this.moveWhenInside || hit) + { + this.dispatchEvent( displayObject, 'touchmove', this.eventData); + } + } - return touchData; -}; + /** + * Grabs an interaction data object from the internal pool + * + * @param touchEvent {object} The touch event we need to pair with an interactionData object + * + * @private + */ + getTouchData(touchEvent) + { + var touchData = this.interactiveDataPool.pop(); -/** - * Returns an interaction data object to the internal pool - * - * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool - * - * @private - */ -InteractionManager.prototype.returnTouchData = function ( touchData ) -{ - this.interactiveDataPool.push( touchData ); -}; + if(!touchData) + { + touchData = new InteractionData(); + } -/** - * Destroys the interaction manager - * - */ -InteractionManager.prototype.destroy = function () { - this.removeEvents(); + touchData.identifier = touchEvent.identifier; + this.mapPositionToPoint( touchData.global, touchEvent.clientX, touchEvent.clientY ); - this.removeAllListeners(); + if(navigator.isCocoonJS) + { + touchData.global.x = touchData.global.x / this.resolution; + touchData.global.y = touchData.global.y / this.resolution; + } - this.renderer = null; + touchEvent.globalX = touchData.global.x; + touchEvent.globalY = touchData.global.y; - this.mouse = null; + return touchData; + } - this.eventData = null; + /** + * Returns an interaction data object to the internal pool + * + * @param touchData {PIXI.interaction.InteractionData} The touch data object we want to return to the pool + * + * @private + */ + returnTouchData( touchData ) + { + this.interactiveDataPool.push( touchData ); + } - this.interactiveDataPool = null; + /** + * Destroys the interaction manager + * + */ + destroy() { + this.removeEvents(); - this.interactionDOMElement = null; + this.removeAllListeners(); - this.onMouseUp = null; - this.processMouseUp = null; + this.renderer = null; + + this.mouse = null; + + this.eventData = null; + + this.interactiveDataPool = null; + + this.interactionDOMElement = null; + + this.onMouseUp = null; + this.processMouseUp = null; - this.onMouseDown = null; - this.processMouseDown = null; + this.onMouseDown = null; + this.processMouseDown = null; - this.onMouseMove = null; - this.processMouseMove = null; + this.onMouseMove = null; + this.processMouseMove = null; - this.onMouseOut = null; - this.processMouseOverOut = null; + this.onMouseOut = null; + this.processMouseOverOut = null; - this.onMouseOver = null; + this.onMouseOver = null; - this.onTouchStart = null; - this.processTouchStart = null; + this.onTouchStart = null; + this.processTouchStart = null; - this.onTouchEnd = null; - this.processTouchEnd = null; + this.onTouchEnd = null; + this.processTouchEnd = null; - this.onTouchMove = null; - this.processTouchMove = null; + this.onTouchMove = null; + this.processTouchMove = null; - this._tempPoint = null; -}; + this._tempPoint = null; + } + +} + +module.exports = InteractionManager; core.WebGLRenderer.registerPlugin('interaction', InteractionManager); core.CanvasRenderer.registerPlugin('interaction', InteractionManager); diff --git a/src/loaders/loader.js b/src/loaders/loader.js index dbce851..9a164b9 100644 --- a/src/loaders/loader.js +++ b/src/loaders/loader.js @@ -26,17 +26,17 @@ * @param [concurrency=10] {number} The number of resources to load concurrently. * @see https://github.com/englercj/resource-loader */ -function Loader(baseUrl, concurrency) -{ - ResourceLoader.call(this, baseUrl, concurrency); +class Loader extends ResourceLoader { + constructor(baseUrl, concurrency) + { + super(baseUrl, concurrency); - for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { - this.use(Loader._pixiMiddleware[i]()); + for (var i = 0; i < Loader._pixiMiddleware.length; ++i) { + this.use(Loader._pixiMiddleware[i]()); + } } -} -Loader.prototype = Object.create(ResourceLoader.prototype); -Loader.prototype.constructor = Loader; +} module.exports = Loader; diff --git a/src/mesh/Mesh.js b/src/mesh/Mesh.js index afbb4b3..0d6e706 100644 --- a/src/mesh/Mesh.js +++ b/src/mesh/Mesh.js @@ -15,101 +15,423 @@ * @param [indices] {Uint16Array} if you want to specify the indices * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts */ -function Mesh(texture, vertices, uvs, indices, drawMode) -{ - core.Container.call(this); +class Mesh extends core.Container { + constructor(texture, vertices, uvs, indices, drawMode) + { + super(); + + /** + * The texture of the Mesh + * + * @member {PIXI.Texture} + * @private + */ + this._texture = null; + + /** + * The Uvs of the Mesh + * + * @member {Float32Array} + */ + this.uvs = uvs || new Float32Array([0, 0, + 1, 0, + 1, 1, + 0, 1]); + + /** + * An array of vertices + * + * @member {Float32Array} + */ + this.vertices = vertices || new Float32Array([0, 0, + 100, 0, + 100, 100, + 0, 100]); + + /* + * @member {Uint16Array} An array containing the indices of the vertices + */ + // TODO auto generate this based on draw mode! + this.indices = indices || new Uint16Array([0, 1, 3, 2]); + + /** + * Whether the Mesh is dirty or not + * + * @member {number} + */ + this.dirty = 0; + this.indexDirty = 0; + + /** + * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. + * + * @member {number} + */ + this.canvasPadding = 0; + + /** + * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts + * + * @member {number} + * @see PIXI.mesh.Mesh.DRAW_MODES + */ + this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + + // run texture setter; + this.texture = texture; + + /** + * The default shader that is used if a mesh doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + + /** + * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * + * @member {number} + * @memberof PIXI.mesh.Mesh# + */ + this.tintRgb = new Float32Array([1, 1, 1]); + + this._glDatas = []; + } /** - * The texture of the Mesh + * Renders the object using the WebGL renderer * - * @member {PIXI.Texture} + * @param renderer {PIXI.WebGLRenderer} a reference to the WebGL renderer * @private */ - this._texture = null; + _renderWebGL(renderer) + { + // get rid of any thing that may be batching. + renderer.flush(); + + // renderer.plugins.mesh.render(this); + var gl = renderer.gl; + var glData = this._glDatas[renderer.CONTEXT_UID]; + + if(!glData) + { + glData = { + shader:new Shader(gl), + vertexBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.vertices, gl.STREAM_DRAW), + uvBuffer:glCore.GLBuffer.createVertexBuffer(gl, this.uvs, gl.STREAM_DRAW), + indexBuffer:glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW), + // build the vao object that will render.. + vao:new glCore.VertexArrayObject(gl), + dirty:this.dirty, + indexDirty:this.indexDirty + }; + + // build the vao object that will render.. + glData.vao = new glCore.VertexArrayObject(gl) + .addIndex(glData.indexBuffer) + .addAttribute(glData.vertexBuffer, glData.shader.attributes.aVertexPosition, gl.FLOAT, false, 2 * 4, 0) + .addAttribute(glData.uvBuffer, glData.shader.attributes.aTextureCoord, gl.FLOAT, false, 2 * 4, 0); + + this._glDatas[renderer.CONTEXT_UID] = glData; + + + } + + if(this.dirty !== glData.dirty) + { + glData.dirty = this.dirty; + glData.uvBuffer.upload(); + + } + + if(this.indexDirty !== glData.indexDirty) + { + glData.indexDirty = this.indexDirty; + glData.indexBuffer.upload(); + } + + glData.vertexBuffer.upload(); + + renderer.bindShader(glData.shader); + renderer.bindTexture(this._texture, 0); + renderer.state.setBlendMode(this.blendMode); + + glData.shader.uniforms.translationMatrix = this.worldTransform.toArray(true); + glData.shader.uniforms.alpha = this.worldAlpha; + glData.shader.uniforms.tint = this.tintRgb; + + var drawMode = this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH ? gl.TRIANGLE_STRIP : gl.TRIANGLES; + + glData.vao.bind() + .draw(drawMode, this.indices.length) + .unbind(); + } /** - * The Uvs of the Mesh + * Renders the object using the Canvas renderer * - * @member {Float32Array} + * @param renderer {PIXI.CanvasRenderer} + * @private */ - this.uvs = uvs || new Float32Array([0, 0, - 1, 0, - 1, 1, - 0, 1]); + _renderCanvas(renderer) + { + var context = renderer.context; + + var transform = this.worldTransform; + var res = renderer.resolution; + + if (renderer.roundPixels) + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0); + } + else + { + context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res); + } + + if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) + { + this._renderCanvasTriangleMesh(context); + } + else + { + this._renderCanvasTriangles(context); + } + } /** - * An array of vertices + * Draws the object in Triangle Mesh mode using canvas * - * @member {Float32Array} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.vertices = vertices || new Float32Array([0, 0, - 100, 0, - 100, 100, - 0, 100]); + _renderCanvasTriangleMesh(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; - /* - * @member {Uint16Array} An array containing the indices of the vertices - */ - // TODO auto generate this based on draw mode! - this.indices = indices || new Uint16Array([0, 1, 3, 2]); + var length = vertices.length / 2; + // this.count++; + + for (var i = 0; i < length - 2; i++) + { + // draw some triangles! + var index = i * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4)); + } + } /** - * Whether the Mesh is dirty or not + * Draws the object in triangle mode using canvas * - * @member {number} + * @param context {CanvasRenderingContext2D} the current drawing context + * @private */ - this.dirty = 0; - this.indexDirty = 0; + _renderCanvasTriangles(context) + { + // draw triangles!! + var vertices = this.vertices; + var uvs = this.uvs; + var indices = this.indices; + + var length = indices.length; + // this.count++; + + for (var i = 0; i < length; i += 3) + { + // draw some triangles! + var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2; + this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2); + } + } /** - * The blend mode to be applied to the sprite. Set to `PIXI.BLEND_MODES.NORMAL` to remove any blend mode. + * Draws one of the triangles that form this Mesh * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES + * @param context {CanvasRenderingContext2D} the current drawing context + * @param vertices {Float32Array} a reference to the vertices of the Mesh + * @param uvs {Float32Array} a reference to the uvs of the Mesh + * @param index0 {number} the index of the first vertex + * @param index1 {number} the index of the second vertex + * @param index2 {number} the index of the third vertex + * @private */ - this.blendMode = core.BLEND_MODES.NORMAL; + _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) + { + var base = this._texture.baseTexture; + var textureSource = base.source; + var textureWidth = base.width; + var textureHeight = base.height; - /** - * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other. - * - * @member {number} - */ - this.canvasPadding = 0; + var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2]; + var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1]; - /** - * The way the Mesh should be drawn, can be any of the {@link PIXI.mesh.Mesh.DRAW_MODES} consts - * - * @member {number} - * @see PIXI.mesh.Mesh.DRAW_MODES - */ - this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH; + var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width; + var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height; - // run texture setter; - this.texture = texture; + if (this.canvasPadding > 0) + { + var paddingX = this.canvasPadding / this.worldTransform.a; + var paddingY = this.canvasPadding / this.worldTransform.d; + var centerX = (x0 + x1 + x2) / 3; + var centerY = (y0 + y1 + y2) / 3; - /** - * The default shader that is used if a mesh doesn't have a more specific one. - * - * @member {PIXI.Shader} - */ - this.shader = null; + var normX = x0 - centerX; + var normY = y0 - centerY; + + var dist = Math.sqrt(normX * normX + normY * normY); + x0 = centerX + (normX / dist) * (dist + paddingX); + y0 = centerY + (normY / dist) * (dist + paddingY); + + // + + normX = x1 - centerX; + normY = y1 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x1 = centerX + (normX / dist) * (dist + paddingX); + y1 = centerY + (normY / dist) * (dist + paddingY); + + normX = x2 - centerX; + normY = y2 - centerY; + + dist = Math.sqrt(normX * normX + normY * normY); + x2 = centerX + (normX / dist) * (dist + paddingX); + y2 = centerY + (normY / dist) * (dist + paddingY); + } + + context.save(); + context.beginPath(); + + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + + context.closePath(); + + context.clip(); + + // Compute matrix transform + var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); + var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); + var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); + var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); + var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); + var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); + var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); + + context.transform(deltaA / delta, deltaD / delta, + deltaB / delta, deltaE / delta, + deltaC / delta, deltaF / delta); + + context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); + context.restore(); + } + /** - * The tint applied to the mesh. This is a [r,g,b] value. A value of [1,1,1] will remove any tint effect. + * Renders a flat Mesh * - * @member {number} - * @memberof PIXI.mesh.Mesh# + * @param Mesh {PIXI.mesh.Mesh} The Mesh to render + * @private */ - this.tintRgb = new Float32Array([1, 1, 1]); + renderMeshFlat(Mesh) + { + var context = this.context; + var vertices = Mesh.vertices; - this._glDatas = []; + var length = vertices.length/2; + // this.count++; + + context.beginPath(); + for (var i=1; i < length-2; i++) + { + // draw some triangles! + var index = i*2; + + var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; + var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; + + context.moveTo(x0, y0); + context.lineTo(x1, y1); + context.lineTo(x2, y2); + } + + context.fillStyle = '#FF0000'; + context.fill(); + context.closePath(); + } + + /** + * When the texture is updated, this event will fire to update the scale and frame + * + * @private + */ + _onTextureUpdate() + { + + } + + /** + * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. + * + * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite + * @return {PIXI.Rectangle} the framing rectangle + */ + _calculateBounds() + { + //TODO - we can cache local bounds and use them if they are dirty (like graphics) + this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); + } + + /** + * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH + * + * @param point {PIXI.Point} the point to test + * @return {boolean} the result of the test + */ + containsPoint( point ) { + if (!this.getBounds().contains(point.x, point.y)) { + return false; + } + this.worldTransform.applyInverse(point, tempPoint); + + var vertices = this.vertices; + var points = tempPolygon.points; + + var indices = this.indices; + var len = this.indices.length; + var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; + for (var i=0;i+2 0) - { - var paddingX = this.canvasPadding / this.worldTransform.a; - var paddingY = this.canvasPadding / this.worldTransform.d; - var centerX = (x0 + x1 + x2) / 3; - var centerY = (y0 + y1 + y2) / 3; - - var normX = x0 - centerX; - var normY = y0 - centerY; - - var dist = Math.sqrt(normX * normX + normY * normY); - x0 = centerX + (normX / dist) * (dist + paddingX); - y0 = centerY + (normY / dist) * (dist + paddingY); - - // - - normX = x1 - centerX; - normY = y1 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x1 = centerX + (normX / dist) * (dist + paddingX); - y1 = centerY + (normY / dist) * (dist + paddingY); - - normX = x2 - centerX; - normY = y2 - centerY; - - dist = Math.sqrt(normX * normX + normY * normY); - x2 = centerX + (normX / dist) * (dist + paddingX); - y2 = centerY + (normY / dist) * (dist + paddingY); - } - - context.save(); - context.beginPath(); - - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - - context.closePath(); - - context.clip(); - - // Compute matrix transform - var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2); - var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2); - var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2); - var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2); - var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2); - var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2); - var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2); - - context.transform(deltaA / delta, deltaD / delta, - deltaB / delta, deltaE / delta, - deltaC / delta, deltaF / delta); - - context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight); - context.restore(); -}; - - - -/** - * Renders a flat Mesh - * - * @param Mesh {PIXI.mesh.Mesh} The Mesh to render - * @private - */ -Mesh.prototype.renderMeshFlat = function (Mesh) -{ - var context = this.context; - var vertices = Mesh.vertices; - - var length = vertices.length/2; - // this.count++; - - context.beginPath(); - for (var i=1; i < length-2; i++) - { - // draw some triangles! - var index = i*2; - - var x0 = vertices[index], x1 = vertices[index+2], x2 = vertices[index+4]; - var y0 = vertices[index+1], y1 = vertices[index+3], y2 = vertices[index+5]; - - context.moveTo(x0, y0); - context.lineTo(x1, y1); - context.lineTo(x2, y2); - } - - context.fillStyle = '#FF0000'; - context.fill(); - context.closePath(); -}; - -/** - * When the texture is updated, this event will fire to update the scale and frame - * - * @private - */ -Mesh.prototype._onTextureUpdate = function () -{ - -}; - -/** - * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account. - * - * @param [matrix=this.worldTransform] {PIXI.Matrix} the transformation matrix of the sprite - * @return {PIXI.Rectangle} the framing rectangle - */ -Mesh.prototype._calculateBounds = function () -{ - //TODO - we can cache local bounds and use them if they are dirty (like graphics) - this._bounds.addVertices(this.transform, this.vertices, 0, this.vertices.length); -}; - -/** - * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH - * - * @param point {PIXI.Point} the point to test - * @return {boolean} the result of the test - */ -Mesh.prototype.containsPoint = function( point ) { - if (!this.getBounds().contains(point.x, point.y)) { - return false; - } - this.worldTransform.applyInverse(point, tempPoint); - - var vertices = this.vertices; - var points = tempPolygon.points; - - var indices = this.indices; - var len = this.indices.length; - var step = this.drawMode === Mesh.DRAW_MODES.TRIANGLES ? 3 : 1; - for (var i=0;i+2 1) + { + ratio = 1; + } + + perpLength = Math.sqrt(perpX * perpX + perpY * perpY); + num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; + perpX /= perpLength; + perpY /= perpLength; + + perpX *= num; + perpY *= num; + + vertices[index] = point.x + perpX; + vertices[index+1] = point.y + perpY; + vertices[index+2] = point.x - perpX; + vertices[index+3] = point.y - perpY; + + lastPoint = point; + } + + this.containerUpdateTransform(); + } + } - -// constructor -Rope.prototype = Object.create(Mesh.prototype); -Rope.prototype.constructor = Rope; module.exports = Rope; - -/** - * Refreshes - * - */ -Rope.prototype.refresh = function () -{ - var points = this.points; - - // if too little points, or texture hasn't got UVs set yet just move on. - if (points.length < 1 || !this._texture._uvs) - { - return; - } - - var uvs = this.uvs; - - var indices = this.indices; - var colors = this.colors; - - var textureUvs = this._texture._uvs; - var offset = new core.Point(textureUvs.x0, textureUvs.y0); - var factor = new core.Point(textureUvs.x2 - textureUvs.x0, textureUvs.y2 - textureUvs.y0); - - uvs[0] = 0 + offset.x; - uvs[1] = 0 + offset.y; - uvs[2] = 0 + offset.x; - uvs[3] = 1 * factor.y + offset.y; - - colors[0] = 1; - colors[1] = 1; - - indices[0] = 0; - indices[1] = 1; - - var total = points.length, - point, index, amount; - - for (var i = 1; i < total; i++) - { - point = points[i]; - index = i * 4; - // time to do some smart drawing! - amount = i / (total-1); - - uvs[index] = amount * factor.x + offset.x; - uvs[index+1] = 0 + offset.y; - - uvs[index+2] = amount * factor.x + offset.x; - uvs[index+3] = 1 * factor.y + offset.y; - - index = i * 2; - colors[index] = 1; - colors[index+1] = 1; - - index = i * 2; - indices[index] = index; - indices[index + 1] = index + 1; - } - - this.dirty = true; - this.indexDirty = true; -}; - -/** - * Clear texture UVs when new texture is set - * - * @private - */ -Rope.prototype._onTextureUpdate = function () -{ - - Mesh.prototype._onTextureUpdate.call(this); - - // wait for the Rope ctor to finish before calling refresh - if (this._ready) { - this.refresh(); - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -Rope.prototype.updateTransform = function () -{ - var points = this.points; - - if (points.length < 1) - { - return; - } - - var lastPoint = points[0]; - var nextPoint; - var perpX = 0; - var perpY = 0; - - // this.count -= 0.2; - - var vertices = this.vertices; - var total = points.length, - point, index, ratio, perpLength, num; - - for (var i = 0; i < total; i++) - { - point = points[i]; - index = i * 4; - - if (i < points.length-1) - { - nextPoint = points[i+1]; - } - else - { - nextPoint = point; - } - - perpY = -(nextPoint.x - lastPoint.x); - perpX = nextPoint.y - lastPoint.y; - - ratio = (1 - (i / (total-1))) * 10; - - if (ratio > 1) - { - ratio = 1; - } - - perpLength = Math.sqrt(perpX * perpX + perpY * perpY); - num = this._texture.height / 2; //(20 + Math.abs(Math.sin((i + this.count) * 0.3) * 50) )* ratio; - perpX /= perpLength; - perpY /= perpLength; - - perpX *= num; - perpY *= num; - - vertices[index] = point.x + perpX; - vertices[index+1] = point.y + perpY; - vertices[index+2] = point.x - perpX; - vertices[index+3] = point.y - perpY; - - lastPoint = point; - } - - this.containerUpdateTransform(); -}; diff --git a/src/mesh/webgl/MeshShader.js b/src/mesh/webgl/MeshShader.js index 0acda45..4fedaef 100644 --- a/src/mesh/webgl/MeshShader.js +++ b/src/mesh/webgl/MeshShader.js @@ -6,41 +6,40 @@ * @memberof PIXI.mesh * @param gl {PIXI.Shader} TODO: Find a good explanation for this. */ -function MeshShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', +class MeshShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', - 'uniform mat3 translationMatrix;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 translationMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', + 'varying vec2 vTextureCoord;', - 'void main(void){', - ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - '}' - ].join('\n'), - [ - 'varying vec2 vTextureCoord;', - 'uniform float alpha;', - 'uniform vec3 tint;', + 'void main(void){', + ' gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);', + ' vTextureCoord = aTextureCoord;', + '}' + ].join('\n'), + [ + 'varying vec2 vTextureCoord;', + 'uniform float alpha;', + 'uniform vec3 tint;', - 'uniform sampler2D uSampler;', + 'uniform sampler2D uSampler;', - 'void main(void){', - ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', - // ' gl_FragColor = vec4(1.0);', - '}' - ].join('\n') - ); + 'void main(void){', + ' gl_FragColor = texture2D(uSampler, vTextureCoord) * vec4(tint * alpha, alpha);', + // ' gl_FragColor = vec4(1.0);', + '}' + ].join('\n') + ); + } } -MeshShader.prototype = Object.create(Shader.prototype); -MeshShader.prototype.constructor = MeshShader; module.exports = MeshShader; - diff --git a/src/particles/ParticleContainer.js b/src/particles/ParticleContainer.js index 688e0a3..3b7c025 100644 --- a/src/particles/ParticleContainer.js +++ b/src/particles/ParticleContainer.js @@ -32,301 +32,302 @@ * @param [properties.alpha=false] {boolean} When true, alpha be uploaded and applied. * @param [batchSize=15000] {number} Number of particles per batch. */ -function ParticleContainer(maxSize, properties, batchSize) -{ - core.Container.call(this); - - batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop - maxSize = maxSize || 15000; - - // Making sure the batch size is valid - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - var maxBatchSize = 16384; - if (batchSize > maxBatchSize) { - batchSize = maxBatchSize; - } - - if (batchSize > maxSize) { - batchSize = maxSize; - } - - /** - * Set properties to be dynamic (true) / static (false) - * - * @member {boolean[]} - * @private - */ - this._properties = [false, true, false, false, false]; - - /** - * @member {number} - * @private - */ - this._maxSize = maxSize; - - /** - * @member {number} - * @private - */ - this._batchSize = batchSize; - - /** - * @member {WebGLBuffer} - * @private - */ - this._glBuffers = []; - - /** - * @member {number} - * @private - */ - this._bufferToUpdate = 0; - - /** - * @member {boolean} - * - */ - this.interactiveChildren = false; - - /** - * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. - * - * @member {number} - * @default PIXI.BLEND_MODES.NORMAL - * @see PIXI.BLEND_MODES - */ - this.blendMode = core.BLEND_MODES.NORMAL; - - /** - * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. - * - * @member {boolean} - * @default true; - */ - this.roundPixels = true; - - this.baseTexture = null; - - this.setProperties(properties); -} - -ParticleContainer.prototype = Object.create(core.Container.prototype); -ParticleContainer.prototype.constructor = ParticleContainer; -module.exports = ParticleContainer; - -/** - * Sets the private properties array to dynamic / static based on the passed properties object - * - * @param properties {object} The properties to be uploaded - */ -ParticleContainer.prototype.setProperties = function(properties) -{ - if ( properties ) { - this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; - this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; - this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; - this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; - this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; - } -}; - -/** - * Updates the object transform for rendering - * - * @private - */ -ParticleContainer.prototype.updateTransform = function () -{ - - // TODO don't need to! - this.displayObjectUpdateTransform(); - // PIXI.Container.prototype.updateTransform.call( this ); -}; - -/** - * Renders the container using the WebGL renderer - * - * @param renderer {PIXI.WebGLRenderer} The webgl renderer - * @private - */ -ParticleContainer.prototype.renderWebGL = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) +class ParticleContainer extends core.Container { + constructor(maxSize, properties, batchSize) { - return; + super(); + + batchSize = batchSize || 15000; //CONST.SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop + maxSize = maxSize || 15000; + + // Making sure the batch size is valid + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + var maxBatchSize = 16384; + if (batchSize > maxBatchSize) { + batchSize = maxBatchSize; + } + + if (batchSize > maxSize) { + batchSize = maxSize; + } + + /** + * Set properties to be dynamic (true) / static (false) + * + * @member {boolean[]} + * @private + */ + this._properties = [false, true, false, false, false]; + + /** + * @member {number} + * @private + */ + this._maxSize = maxSize; + + /** + * @member {number} + * @private + */ + this._batchSize = batchSize; + + /** + * @member {WebGLBuffer} + * @private + */ + this._glBuffers = []; + + /** + * @member {number} + * @private + */ + this._bufferToUpdate = 0; + + /** + * @member {boolean} + * + */ + this.interactiveChildren = false; + + /** + * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode. + * + * @member {number} + * @default PIXI.BLEND_MODES.NORMAL + * @see PIXI.BLEND_MODES + */ + this.blendMode = core.BLEND_MODES.NORMAL; + + /** + * Used for canvas renderering. If true then the elements will be positioned at the nearest pixel. This provides a nice speed boost. + * + * @member {boolean} + * @default true; + */ + this.roundPixels = true; + + this.baseTexture = null; + + this.setProperties(properties); } - - if(!this.baseTexture) + /** + * Sets the private properties array to dynamic / static based on the passed properties object + * + * @param properties {object} The properties to be uploaded + */ + setProperties(properties) { - this.baseTexture = this.children[0]._texture.baseTexture; - if(!this.baseTexture.hasLoaded) - { - this.baseTexture.once('update', function(){ - this.onChildrenChange(0); - }, this); + if ( properties ) { + this._properties[0] = 'scale' in properties ? !!properties.scale : this._properties[0]; + this._properties[1] = 'position' in properties ? !!properties.position : this._properties[1]; + this._properties[2] = 'rotation' in properties ? !!properties.rotation : this._properties[2]; + this._properties[3] = 'uvs' in properties ? !!properties.uvs : this._properties[3]; + this._properties[4] = 'alpha' in properties ? !!properties.alpha : this._properties[4]; } } - - renderer.setObjectRenderer( renderer.plugins.particle ); - renderer.plugins.particle.render( this ); -}; - -/** - * Set the flag that static data should be updated to true - * - * @private - */ -ParticleContainer.prototype.onChildrenChange = function (smallestChildIndex) -{ - var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); - if (bufferIndex < this._bufferToUpdate) { - this._bufferToUpdate = bufferIndex; - } -}; - -/** - * Renders the object using the Canvas renderer - * - * @param renderer {PIXI.CanvasRenderer} The canvas renderer - * @private - */ -ParticleContainer.prototype.renderCanvas = function (renderer) -{ - if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + /** + * Updates the object transform for rendering + * + * @private + */ + updateTransform() { - return; + + // TODO don't need to! + this.displayObjectUpdateTransform(); + // PIXI.Container.prototype.updateTransform.call( this ); } - var context = renderer.context; - var transform = this.worldTransform; - var isRotated = true; - - var positionX = 0; - var positionY = 0; - - var finalWidth = 0; - var finalHeight = 0; - - var compositeOperation = renderer.blendModes[this.blendMode]; - if (compositeOperation !== context.globalCompositeOperation) + /** + * Renders the container using the WebGL renderer + * + * @param renderer {PIXI.WebGLRenderer} The webgl renderer + * @private + */ + renderWebGL(renderer) { - context.globalCompositeOperation = compositeOperation; - } - - context.globalAlpha = this.worldAlpha; - - this.displayObjectUpdateTransform(); - - for (var i = 0; i < this.children.length; ++i) - { - var child = this.children[i]; - - if (!child.visible) + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) { - continue; + return; } - var frame = child.texture.frame; - context.globalAlpha = this.worldAlpha * child.alpha; - - if (child.rotation % (Math.PI * 2) === 0) + if(!this.baseTexture) { - // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call - if (isRotated) + this.baseTexture = this.children[0]._texture.baseTexture; + if(!this.baseTexture.hasLoaded) { - context.setTransform( - transform.a, - transform.b, - transform.c, - transform.d, - transform.tx * renderer.resolution, - transform.ty * renderer.resolution - ); + this.baseTexture.once('update', function(){ + this.onChildrenChange(0); + }, this); + } + } - isRotated = false; + + renderer.setObjectRenderer( renderer.plugins.particle ); + renderer.plugins.particle.render( this ); + } + + /** + * Set the flag that static data should be updated to true + * + * @private + */ + onChildrenChange(smallestChildIndex) + { + var bufferIndex = Math.floor(smallestChildIndex / this._batchSize); + if (bufferIndex < this._bufferToUpdate) { + this._bufferToUpdate = bufferIndex; + } + } + + /** + * Renders the object using the Canvas renderer + * + * @param renderer {PIXI.CanvasRenderer} The canvas renderer + * @private + */ + renderCanvas(renderer) + { + if (!this.visible || this.worldAlpha <= 0 || !this.children.length || !this.renderable) + { + return; + } + + var context = renderer.context; + var transform = this.worldTransform; + var isRotated = true; + + var positionX = 0; + var positionY = 0; + + var finalWidth = 0; + var finalHeight = 0; + + var compositeOperation = renderer.blendModes[this.blendMode]; + if (compositeOperation !== context.globalCompositeOperation) + { + context.globalCompositeOperation = compositeOperation; + } + + context.globalAlpha = this.worldAlpha; + + this.displayObjectUpdateTransform(); + + for (var i = 0; i < this.children.length; ++i) + { + var child = this.children[i]; + + if (!child.visible) + { + continue; } - positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); - positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); + var frame = child.texture.frame; - finalWidth = frame.width * child.scale.x; - finalHeight = frame.height * child.scale.y; + context.globalAlpha = this.worldAlpha * child.alpha; - } - else - { - if (!isRotated) + if (child.rotation % (Math.PI * 2) === 0) { - isRotated = true; - } + // this is the fastest way to optimise! - if rotation is 0 then we can avoid any kind of setTransform call + if (isRotated) + { + context.setTransform( + transform.a, + transform.b, + transform.c, + transform.d, + transform.tx * renderer.resolution, + transform.ty * renderer.resolution + ); - child.displayObjectUpdateTransform(); + isRotated = false; + } - var childTransform = child.worldTransform; + positionX = ((child.anchor.x) * (-frame.width * child.scale.x) + child.position.x + 0.5); + positionY = ((child.anchor.y) * (-frame.height * child.scale.y) + child.position.y + 0.5); - if (renderer.roundPixels) - { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - (childTransform.tx * renderer.resolution) | 0, - (childTransform.ty * renderer.resolution) | 0 - ); + finalWidth = frame.width * child.scale.x; + finalHeight = frame.height * child.scale.y; + } else { - context.setTransform( - childTransform.a, - childTransform.b, - childTransform.c, - childTransform.d, - childTransform.tx * renderer.resolution, - childTransform.ty * renderer.resolution - ); + if (!isRotated) + { + isRotated = true; + } + + child.displayObjectUpdateTransform(); + + var childTransform = child.worldTransform; + + if (renderer.roundPixels) + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + (childTransform.tx * renderer.resolution) | 0, + (childTransform.ty * renderer.resolution) | 0 + ); + } + else + { + context.setTransform( + childTransform.a, + childTransform.b, + childTransform.c, + childTransform.d, + childTransform.tx * renderer.resolution, + childTransform.ty * renderer.resolution + ); + } + + positionX = ((child.anchor.x) * (-frame.width) + 0.5); + positionY = ((child.anchor.y) * (-frame.height) + 0.5); + + finalWidth = frame.width; + finalHeight = frame.height; } - positionX = ((child.anchor.x) * (-frame.width) + 0.5); - positionY = ((child.anchor.y) * (-frame.height) + 0.5); + var resolution = child.texture.baseTexture.resolution; - finalWidth = frame.width; - finalHeight = frame.height; - } - - var resolution = child.texture.baseTexture.resolution; - - context.drawImage( - child.texture.baseTexture.source, - frame.x * resolution, - frame.y * resolution, - frame.width * resolution, - frame.height * resolution, - positionX * resolution, - positionY * resolution, - finalWidth * resolution, - finalHeight * resolution - ); - } -}; - -/** - * Destroys the container - * - */ -ParticleContainer.prototype.destroy = function () { - core.Container.prototype.destroy.apply(this, arguments); - - if (this._buffers) { - for (var i = 0; i < this._buffers.length; ++i) { - this._buffers[i].destroy(); + context.drawImage( + child.texture.baseTexture.source, + frame.x * resolution, + frame.y * resolution, + frame.width * resolution, + frame.height * resolution, + positionX * resolution, + positionY * resolution, + finalWidth * resolution, + finalHeight * resolution + ); } } - this._properties = null; - this._buffers = null; -}; + /** + * Destroys the container + * + */ + destroy() { + super.destroy(arguments); + + if (this._buffers) { + for (var i = 0; i < this._buffers.length; ++i) { + this._buffers[i].destroy(); + } + } + + this._properties = null; + this._buffers = null; + } + +} + +module.exports = ParticleContainer; diff --git a/src/particles/webgl/ParticleBuffer.js b/src/particles/webgl/ParticleBuffer.js index c9c7fd5..d3ea376 100644 --- a/src/particles/webgl/ParticleBuffer.js +++ b/src/particles/webgl/ParticleBuffer.js @@ -19,211 +19,213 @@ * @private * @memberof PIXI */ -function ParticleBuffer(gl, properties, dynamicPropertyFlags, size) -{ - /** - * The current WebGL drawing context. - * - * @member {WebGLRenderingContext} - */ - this.gl = gl; - - /** - * Size of a single vertex. - * - * @member {number} - */ - this.vertSize = 2; - - /** - * Size of a single vertex in bytes. - * - * @member {number} - */ - this.vertByteSize = this.vertSize * 4; - - /** - * The number of particles the buffer can hold - * - * @member {number} - */ - this.size = size; - - /** - * A list of the properties that are dynamic. - * - * @member {object[]} - */ - this.dynamicProperties = []; - - /** - * A list of the properties that are static. - * - * @member {object[]} - */ - this.staticProperties = []; - - for (var i = 0; i < properties.length; i++) +class ParticleBuffer { + constructor(gl, properties, dynamicPropertyFlags, size) { - var property = properties[i]; + /** + * The current WebGL drawing context. + * + * @member {WebGLRenderingContext} + */ + this.gl = gl; - // Make copy of properties object so that when we edit the offset it doesn't - // change all other instances of the object literal - property = - { - attribute:property.attribute, - size:property.size, - uploadFunction:property.uploadFunction, - offset:property.offset - }; + /** + * Size of a single vertex. + * + * @member {number} + */ + this.vertSize = 2; - if(dynamicPropertyFlags[i]) + /** + * Size of a single vertex in bytes. + * + * @member {number} + */ + this.vertByteSize = this.vertSize * 4; + + /** + * The number of particles the buffer can hold + * + * @member {number} + */ + this.size = size; + + /** + * A list of the properties that are dynamic. + * + * @member {object[]} + */ + this.dynamicProperties = []; + + /** + * A list of the properties that are static. + * + * @member {object[]} + */ + this.staticProperties = []; + + for (var i = 0; i < properties.length; i++) { - this.dynamicProperties.push(property); + var property = properties[i]; + + // Make copy of properties object so that when we edit the offset it doesn't + // change all other instances of the object literal + property = + { + attribute:property.attribute, + size:property.size, + uploadFunction:property.uploadFunction, + offset:property.offset + }; + + if(dynamicPropertyFlags[i]) + { + this.dynamicProperties.push(property); + } + else + { + this.staticProperties.push(property); + } } - else + + this.staticStride = 0; + this.staticBuffer = null; + this.staticData = null; + + this.dynamicStride = 0; + this.dynamicBuffer = null; + this.dynamicData = null; + + this.initBuffers(); + + } + + /** + * Sets up the renderer context and necessary buffers. + * + * @private + */ + initBuffers() + { + var gl = this.gl; + var i; + var property; + + var dynamicOffset = 0; + + + /** + * Holds the indices of the geometry (quads) to draw + * + * @member {Uint16Array} + */ + this.indices = createIndicesForQuads(this.size); + this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); + + + this.dynamicStride = 0; + + for (i = 0; i < this.dynamicProperties.length; i++) { - this.staticProperties.push(property); + property = this.dynamicProperties[i]; + + property.offset = dynamicOffset; + dynamicOffset += property.size; + this.dynamicStride += property.size; + } + + this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); + this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); + + // static // + var staticOffset = 0; + this.staticStride = 0; + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + + property.offset = staticOffset; + staticOffset += property.size; + this.staticStride += property.size; + + + } + + this.staticData = new Float32Array( this.size * this.staticStride * 4); + this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); + + + this.vao = new glCore.VertexArrayObject(gl) + .addIndex(this.indexBuffer); + + for (i = 0; i < this.dynamicProperties.length; i++) + { + property = this.dynamicProperties[i]; + this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); + } + + for (i = 0; i < this.staticProperties.length; i++) + { + property = this.staticProperties[i]; + this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); } } - this.staticStride = 0; - this.staticBuffer = null; - this.staticData = null; + /** + * Uploads the dynamic properties. + * + */ + uploadDynamic(children, startIndex, amount) + { + for (var i = 0; i < this.dynamicProperties.length; i++) + { + var property = this.dynamicProperties[i]; + property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); + } - this.dynamicStride = 0; - this.dynamicBuffer = null; - this.dynamicData = null; + this.dynamicBuffer.upload(); + } - this.initBuffers(); + /** + * Uploads the static properties. + * + */ + uploadStatic(children, startIndex, amount) + { + for (var i = 0; i < this.staticProperties.length; i++) + { + var property = this.staticProperties[i]; + property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); + } + + this.staticBuffer.upload(); + } + + /** + * Binds the buffers to the GPU + * + */ + bind() + { + this.vao.bind(); + } + + /** + * Destroys the ParticleBuffer. + * + */ + destroy() + { + this.dynamicProperties = null; + this.dynamicData = null; + this.dynamicBuffer.destroy(); + + this.staticProperties = null; + this.staticData = null; + this.staticBuffer.destroy(); + } } -ParticleBuffer.prototype.constructor = ParticleBuffer; module.exports = ParticleBuffer; - -/** - * Sets up the renderer context and necessary buffers. - * - * @private - */ -ParticleBuffer.prototype.initBuffers = function () -{ - var gl = this.gl; - var i; - var property; - - var dynamicOffset = 0; - - - /** - * Holds the indices of the geometry (quads) to draw - * - * @member {Uint16Array} - */ - this.indices = createIndicesForQuads(this.size); - this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW); - - - this.dynamicStride = 0; - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - - property.offset = dynamicOffset; - dynamicOffset += property.size; - this.dynamicStride += property.size; - } - - this.dynamicData = new Float32Array( this.size * this.dynamicStride * 4); - this.dynamicBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.dynamicData, gl.STREAM_DRAW); - - // static // - var staticOffset = 0; - this.staticStride = 0; - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - - property.offset = staticOffset; - staticOffset += property.size; - this.staticStride += property.size; - - - } - - this.staticData = new Float32Array( this.size * this.staticStride * 4); - this.staticBuffer = glCore.GLBuffer.createVertexBuffer(gl, this.staticData, gl.STATIC_DRAW); - - - this.vao = new glCore.VertexArrayObject(gl) - .addIndex(this.indexBuffer); - - for (i = 0; i < this.dynamicProperties.length; i++) - { - property = this.dynamicProperties[i]; - this.vao.addAttribute(this.dynamicBuffer, property.attribute, gl.FLOAT, false, this.dynamicStride * 4, property.offset * 4); - } - - for (i = 0; i < this.staticProperties.length; i++) - { - property = this.staticProperties[i]; - this.vao.addAttribute(this.staticBuffer, property.attribute, gl.FLOAT, false, this.staticStride * 4, property.offset * 4); - } -}; - -/** - * Uploads the dynamic properties. - * - */ -ParticleBuffer.prototype.uploadDynamic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.dynamicProperties.length; i++) - { - var property = this.dynamicProperties[i]; - property.uploadFunction(children, startIndex, amount, this.dynamicData, this.dynamicStride, property.offset); - } - - this.dynamicBuffer.upload(); -}; - -/** - * Uploads the static properties. - * - */ -ParticleBuffer.prototype.uploadStatic = function(children, startIndex, amount) -{ - for (var i = 0; i < this.staticProperties.length; i++) - { - var property = this.staticProperties[i]; - property.uploadFunction(children, startIndex, amount, this.staticData, this.staticStride, property.offset); - } - - this.staticBuffer.upload(); -}; - -/** - * Binds the buffers to the GPU - * - */ -ParticleBuffer.prototype.bind = function () -{ - this.vao.bind(); -}; - -/** - * Destroys the ParticleBuffer. - * - */ -ParticleBuffer.prototype.destroy = function () -{ - this.dynamicProperties = null; - this.dynamicData = null; - this.dynamicBuffer.destroy(); - - this.staticProperties = null; - this.staticData = null; - this.staticBuffer.destroy(); -}; diff --git a/src/particles/webgl/ParticleRenderer.js b/src/particles/webgl/ParticleRenderer.js index 6fb921c..5f0281a 100644 --- a/src/particles/webgl/ParticleRenderer.js +++ b/src/particles/webgl/ParticleRenderer.js @@ -20,411 +20,412 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} The renderer this sprite batch works for. */ -function ParticleRenderer(renderer) -{ - core.ObjectRenderer.call(this, renderer); +class ParticleRenderer extends core.ObjectRenderer { + constructor(renderer) + { + super(renderer); - // 65535 is max vertex index in the index buffer (see ParticleRenderer) - // so max number of particles is 65536 / 4 = 16384 - // and max number of element in the index buffer is 16384 * 6 = 98304 - // Creating a full index buffer, overhead is 98304 * 2 = 196Ko - // var numIndices = 98304; + // 65535 is max vertex index in the index buffer (see ParticleRenderer) + // so max number of particles is 65536 / 4 = 16384 + // and max number of element in the index buffer is 16384 * 6 = 98304 + // Creating a full index buffer, overhead is 98304 * 2 = 196Ko + // var numIndices = 98304; + + /** + * The default shader that is used if a sprite doesn't have a more specific one. + * + * @member {PIXI.Shader} + */ + this.shader = null; + + this.indexBuffer = null; + + this.properties = null; + + this.tempMatrix = new core.Matrix(); + + this.CONTEXT_UID = 0; + } /** - * The default shader that is used if a sprite doesn't have a more specific one. + * When there is a WebGL context change * - * @member {PIXI.Shader} + * @private */ - this.shader = null; + onContextChange() + { + var gl = this.renderer.gl; - this.indexBuffer = null; + this.CONTEXT_UID = this.renderer.CONTEXT_UID; - this.properties = null; + // setup default shader + this.shader = new ParticleShader(gl); - this.tempMatrix = new core.Matrix(); + this.properties = [ + // verticesData + { + attribute:this.shader.attributes.aVertexPosition, + size:2, + uploadFunction:this.uploadVertices, + offset:0 + }, + // positionData + { + attribute:this.shader.attributes.aPositionCoord, + size:2, + uploadFunction:this.uploadPosition, + offset:0 + }, + // rotationData + { + attribute:this.shader.attributes.aRotation, + size:1, + uploadFunction:this.uploadRotation, + offset:0 + }, + // uvsData + { + attribute:this.shader.attributes.aTextureCoord, + size:2, + uploadFunction:this.uploadUvs, + offset:0 + }, + // alphaData + { + attribute:this.shader.attributes.aColor, + size:1, + uploadFunction:this.uploadAlpha, + offset:0 + } + ]; - this.CONTEXT_UID = 0; + } + + /** + * Starts a new particle batch. + * + */ + start() + { + this.renderer.bindShader(this.shader); + } + + + /** + * Renders the particle container object. + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ + render(container) + { + var children = container.children, + totalChildren = children.length, + maxSize = container._maxSize, + batchSize = container._batchSize; + + if(totalChildren === 0) + { + return; + } + else if(totalChildren > maxSize) + { + totalChildren = maxSize; + } + + var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; + + if(!buffers) + { + buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); + } + + // if the uvs have not updated then no point rendering just yet! + this.renderer.setBlendMode(container.blendMode); + + var gl = this.renderer.gl; + + var m = container.worldTransform.copy( this.tempMatrix ); + m.prepend( this.renderer._activeRenderTarget.projectionMatrix ); + this.shader.uniforms.projectionMatrix = m.toArray(true); + this.shader.uniforms.uAlpha = container.worldAlpha; + + + // make sure the texture is bound.. + var baseTexture = children[0]._texture.baseTexture; + + this.renderer.bindTexture(baseTexture); + + // now lets upload and render the buffers.. + for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) + { + var amount = ( totalChildren - i); + if(amount > batchSize) + { + amount = batchSize; + } + + var buffer = buffers[j]; + + // we always upload the dynamic + buffer.uploadDynamic(children, i, amount); + + // we only upload the static content when we have to! + if(container._bufferToUpdate === j) + { + buffer.uploadStatic(children, i, amount); + container._bufferToUpdate = j + 1; + } + + // bind the buffer + buffer.vao.bind() + .draw(gl.TRIANGLES, amount * 6) + .unbind(); + + // now draw those suckas! + // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); + // this.renderer.drawCount++; + } + } + + /** + * Creates one particle buffer for each child in the container we want to render and updates internal properties + * + * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer + */ + generateBuffers(container) + { + var gl = this.renderer.gl, + buffers = [], + size = container._maxSize, + batchSize = container._batchSize, + dynamicPropertyFlags = container._properties, + i; + + for (i = 0; i < size; i += batchSize) + { + buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); + } + + return buffers; + } + + /** + * Uploads the verticies. + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their vertices uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadVertices(children, startIndex, amount, array, stride, offset) + { + var sprite, + texture, + trim, + orig, + sx, + sy, + w0, w1, h0, h1; + + for (var i = 0; i < amount; i++) { + + sprite = children[startIndex + i]; + texture = sprite._texture; + sx = sprite.scale.x; + sy = sprite.scale.y; + trim = texture.trim; + orig = texture.orig; + + if (trim) + { + // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. + w1 = trim.x - sprite.anchor.x * orig.width; + w0 = w1 + trim.width; + + h1 = trim.y - sprite.anchor.y * orig.height; + h0 = h1 + trim.height; + + } + else + { + w0 = (orig.width ) * (1-sprite.anchor.x); + w1 = (orig.width ) * -sprite.anchor.x; + + h0 = orig.height * (1-sprite.anchor.y); + h1 = orig.height * -sprite.anchor.y; + } + + array[offset] = w1 * sx; + array[offset + 1] = h1 * sy; + + array[offset + stride] = w0 * sx; + array[offset + stride + 1] = h1 * sy; + + array[offset + stride * 2] = w0 * sx; + array[offset + stride * 2 + 1] = h0 * sy; + + array[offset + stride * 3] = w1 * sx; + array[offset + stride * 3 + 1] = h0 * sy; + + offset += stride * 4; + } + + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their positions uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadPosition(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spritePosition = children[startIndex + i].position; + + array[offset] = spritePosition.x; + array[offset + 1] = spritePosition.y; + + array[offset + stride] = spritePosition.x; + array[offset + stride + 1] = spritePosition.y; + + array[offset + stride * 2] = spritePosition.x; + array[offset + stride * 2 + 1] = spritePosition.y; + + array[offset + stride * 3] = spritePosition.x; + array[offset + stride * 3 + 1] = spritePosition.y; + + offset += stride * 4; + } + + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their rotation uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadRotation(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spriteRotation = children[startIndex + i].rotation; + + + array[offset] = spriteRotation; + array[offset + stride] = spriteRotation; + array[offset + stride * 2] = spriteRotation; + array[offset + stride * 3] = spriteRotation; + + offset += stride * 4; + } + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their Uvs uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadUvs(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var textureUvs = children[startIndex + i]._texture._uvs; + + if (textureUvs) + { + array[offset] = textureUvs.x0; + array[offset + 1] = textureUvs.y0; + + array[offset + stride] = textureUvs.x1; + array[offset + stride + 1] = textureUvs.y1; + + array[offset + stride * 2] = textureUvs.x2; + array[offset + stride * 2 + 1] = textureUvs.y2; + + array[offset + stride * 3] = textureUvs.x3; + array[offset + stride * 3 + 1] = textureUvs.y3; + + offset += stride * 4; + } + else + { + //TODO you know this can be easier! + array[offset] = 0; + array[offset + 1] = 0; + + array[offset + stride] = 0; + array[offset + stride + 1] = 0; + + array[offset + stride * 2] = 0; + array[offset + stride * 2 + 1] = 0; + + array[offset + stride * 3] = 0; + array[offset + stride * 3 + 1] = 0; + + offset += stride * 4; + } + } + } + + /** + * + * @param children {PIXI.DisplayObject[]} the array of display objects to render + * @param startIndex {number} the index to start from in the children array + * @param amount {number} the amount of children that will have their alpha uploaded + * @param array {number[]} + * @param stride {number} + * @param offset {number} + */ + uploadAlpha(children,startIndex, amount, array, stride, offset) + { + for (var i = 0; i < amount; i++) + { + var spriteAlpha = children[startIndex + i].alpha; + + array[offset] = spriteAlpha; + array[offset + stride] = spriteAlpha; + array[offset + stride * 2] = spriteAlpha; + array[offset + stride * 3] = spriteAlpha; + + offset += stride * 4; + } + } + + + /** + * Destroys the ParticleRenderer. + * + */ + destroy() + { + if (this.renderer.gl) { + this.renderer.gl.deleteBuffer(this.indexBuffer); + } + core.ObjectRenderer.prototype.destroy.apply(this, arguments); + + this.shader.destroy(); + + this.indices = null; + this.tempMatrix = null; + } + } -ParticleRenderer.prototype = Object.create(core.ObjectRenderer.prototype); -ParticleRenderer.prototype.constructor = ParticleRenderer; module.exports = ParticleRenderer; core.WebGLRenderer.registerPlugin('particle', ParticleRenderer); - -/** - * When there is a WebGL context change - * - * @private - */ -ParticleRenderer.prototype.onContextChange = function () -{ - var gl = this.renderer.gl; - - this.CONTEXT_UID = this.renderer.CONTEXT_UID; - - // setup default shader - this.shader = new ParticleShader(gl); - - this.properties = [ - // verticesData - { - attribute:this.shader.attributes.aVertexPosition, - size:2, - uploadFunction:this.uploadVertices, - offset:0 - }, - // positionData - { - attribute:this.shader.attributes.aPositionCoord, - size:2, - uploadFunction:this.uploadPosition, - offset:0 - }, - // rotationData - { - attribute:this.shader.attributes.aRotation, - size:1, - uploadFunction:this.uploadRotation, - offset:0 - }, - // uvsData - { - attribute:this.shader.attributes.aTextureCoord, - size:2, - uploadFunction:this.uploadUvs, - offset:0 - }, - // alphaData - { - attribute:this.shader.attributes.aColor, - size:1, - uploadFunction:this.uploadAlpha, - offset:0 - } - ]; - -}; - -/** - * Starts a new particle batch. - * - */ -ParticleRenderer.prototype.start = function () -{ - this.renderer.bindShader(this.shader); -}; - - -/** - * Renders the particle container object. - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.render = function (container) -{ - var children = container.children, - totalChildren = children.length, - maxSize = container._maxSize, - batchSize = container._batchSize; - - if(totalChildren === 0) - { - return; - } - else if(totalChildren > maxSize) - { - totalChildren = maxSize; - } - - var buffers = container._glBuffers[this.renderer.CONTEXT_UID]; - - if(!buffers) - { - buffers = container._glBuffers[this.renderer.CONTEXT_UID] = this.generateBuffers( container ); - } - - // if the uvs have not updated then no point rendering just yet! - this.renderer.setBlendMode(container.blendMode); - - var gl = this.renderer.gl; - - var m = container.worldTransform.copy( this.tempMatrix ); - m.prepend( this.renderer._activeRenderTarget.projectionMatrix ); - this.shader.uniforms.projectionMatrix = m.toArray(true); - this.shader.uniforms.uAlpha = container.worldAlpha; - - - // make sure the texture is bound.. - var baseTexture = children[0]._texture.baseTexture; - - this.renderer.bindTexture(baseTexture); - - // now lets upload and render the buffers.. - for (var i = 0, j = 0; i < totalChildren; i += batchSize, j += 1) - { - var amount = ( totalChildren - i); - if(amount > batchSize) - { - amount = batchSize; - } - - var buffer = buffers[j]; - - // we always upload the dynamic - buffer.uploadDynamic(children, i, amount); - - // we only upload the static content when we have to! - if(container._bufferToUpdate === j) - { - buffer.uploadStatic(children, i, amount); - container._bufferToUpdate = j + 1; - } - - // bind the buffer - buffer.vao.bind() - .draw(gl.TRIANGLES, amount * 6) - .unbind(); - - // now draw those suckas! - // gl.drawElements(gl.TRIANGLES, amount * 6, gl.UNSIGNED_SHORT, 0); - // this.renderer.drawCount++; - } -}; - -/** - * Creates one particle buffer for each child in the container we want to render and updates internal properties - * - * @param container {PIXI.ParticleContainer} The container to render using this ParticleRenderer - */ -ParticleRenderer.prototype.generateBuffers = function (container) -{ - var gl = this.renderer.gl, - buffers = [], - size = container._maxSize, - batchSize = container._batchSize, - dynamicPropertyFlags = container._properties, - i; - - for (i = 0; i < size; i += batchSize) - { - buffers.push(new ParticleBuffer(gl, this.properties, dynamicPropertyFlags, batchSize)); - } - - return buffers; -}; - -/** - * Uploads the verticies. - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their vertices uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadVertices = function (children, startIndex, amount, array, stride, offset) -{ - var sprite, - texture, - trim, - orig, - sx, - sy, - w0, w1, h0, h1; - - for (var i = 0; i < amount; i++) { - - sprite = children[startIndex + i]; - texture = sprite._texture; - sx = sprite.scale.x; - sy = sprite.scale.y; - trim = texture.trim; - orig = texture.orig; - - if (trim) - { - // if the sprite is trimmed and is not a tilingsprite then we need to add the extra space before transforming the sprite coords.. - w1 = trim.x - sprite.anchor.x * orig.width; - w0 = w1 + trim.width; - - h1 = trim.y - sprite.anchor.y * orig.height; - h0 = h1 + trim.height; - - } - else - { - w0 = (orig.width ) * (1-sprite.anchor.x); - w1 = (orig.width ) * -sprite.anchor.x; - - h0 = orig.height * (1-sprite.anchor.y); - h1 = orig.height * -sprite.anchor.y; - } - - array[offset] = w1 * sx; - array[offset + 1] = h1 * sy; - - array[offset + stride] = w0 * sx; - array[offset + stride + 1] = h1 * sy; - - array[offset + stride * 2] = w0 * sx; - array[offset + stride * 2 + 1] = h0 * sy; - - array[offset + stride * 3] = w1 * sx; - array[offset + stride * 3 + 1] = h0 * sy; - - offset += stride * 4; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their positions uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadPosition = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spritePosition = children[startIndex + i].position; - - array[offset] = spritePosition.x; - array[offset + 1] = spritePosition.y; - - array[offset + stride] = spritePosition.x; - array[offset + stride + 1] = spritePosition.y; - - array[offset + stride * 2] = spritePosition.x; - array[offset + stride * 2 + 1] = spritePosition.y; - - array[offset + stride * 3] = spritePosition.x; - array[offset + stride * 3 + 1] = spritePosition.y; - - offset += stride * 4; - } - -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their rotation uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadRotation = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spriteRotation = children[startIndex + i].rotation; - - - array[offset] = spriteRotation; - array[offset + stride] = spriteRotation; - array[offset + stride * 2] = spriteRotation; - array[offset + stride * 3] = spriteRotation; - - offset += stride * 4; - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their Uvs uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadUvs = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var textureUvs = children[startIndex + i]._texture._uvs; - - if (textureUvs) - { - array[offset] = textureUvs.x0; - array[offset + 1] = textureUvs.y0; - - array[offset + stride] = textureUvs.x1; - array[offset + stride + 1] = textureUvs.y1; - - array[offset + stride * 2] = textureUvs.x2; - array[offset + stride * 2 + 1] = textureUvs.y2; - - array[offset + stride * 3] = textureUvs.x3; - array[offset + stride * 3 + 1] = textureUvs.y3; - - offset += stride * 4; - } - else - { - //TODO you know this can be easier! - array[offset] = 0; - array[offset + 1] = 0; - - array[offset + stride] = 0; - array[offset + stride + 1] = 0; - - array[offset + stride * 2] = 0; - array[offset + stride * 2 + 1] = 0; - - array[offset + stride * 3] = 0; - array[offset + stride * 3 + 1] = 0; - - offset += stride * 4; - } - } -}; - -/** - * - * @param children {PIXI.DisplayObject[]} the array of display objects to render - * @param startIndex {number} the index to start from in the children array - * @param amount {number} the amount of children that will have their alpha uploaded - * @param array {number[]} - * @param stride {number} - * @param offset {number} - */ -ParticleRenderer.prototype.uploadAlpha = function (children,startIndex, amount, array, stride, offset) -{ - for (var i = 0; i < amount; i++) - { - var spriteAlpha = children[startIndex + i].alpha; - - array[offset] = spriteAlpha; - array[offset + stride] = spriteAlpha; - array[offset + stride * 2] = spriteAlpha; - array[offset + stride * 3] = spriteAlpha; - - offset += stride * 4; - } -}; - - -/** - * Destroys the ParticleRenderer. - * - */ -ParticleRenderer.prototype.destroy = function () -{ - if (this.renderer.gl) { - this.renderer.gl.deleteBuffer(this.indexBuffer); - } - core.ObjectRenderer.prototype.destroy.apply(this, arguments); - - this.shader.destroy(); - - this.indices = null; - this.tempMatrix = null; -}; diff --git a/src/particles/webgl/ParticleShader.js b/src/particles/webgl/ParticleShader.js index 4c98aab..12eefb7 100644 --- a/src/particles/webgl/ParticleShader.js +++ b/src/particles/webgl/ParticleShader.js @@ -6,59 +6,58 @@ * @memberof PIXI * @param gl {PIXI.Shader} The webgl shader manager this shader works for. */ -function ParticleShader(gl) -{ - Shader.call(this, - gl, - // vertex shader - [ - 'attribute vec2 aVertexPosition;', - 'attribute vec2 aTextureCoord;', - 'attribute float aColor;', +class ParticleShader extends Shader { + constructor(gl) + { + super( + gl, + // vertex shader + [ + 'attribute vec2 aVertexPosition;', + 'attribute vec2 aTextureCoord;', + 'attribute float aColor;', - 'attribute vec2 aPositionCoord;', - 'attribute vec2 aScale;', - 'attribute float aRotation;', + 'attribute vec2 aPositionCoord;', + 'attribute vec2 aScale;', + 'attribute float aRotation;', - 'uniform mat3 projectionMatrix;', + 'uniform mat3 projectionMatrix;', - 'varying vec2 vTextureCoord;', - 'varying float vColor;', + 'varying vec2 vTextureCoord;', + 'varying float vColor;', - 'void main(void){', - ' vec2 v = aVertexPosition;', + 'void main(void){', + ' vec2 v = aVertexPosition;', - ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', - ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', - ' v = v + aPositionCoord;', + ' v.x = (aVertexPosition.x) * cos(aRotation) - (aVertexPosition.y) * sin(aRotation);', + ' v.y = (aVertexPosition.x) * sin(aRotation) + (aVertexPosition.y) * cos(aRotation);', + ' v = v + aPositionCoord;', - ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', + ' gl_Position = vec4((projectionMatrix * vec3(v, 1.0)).xy, 0.0, 1.0);', - ' vTextureCoord = aTextureCoord;', - ' vColor = aColor;', - '}' - ].join('\n'), - // hello - [ - 'varying vec2 vTextureCoord;', - 'varying float vColor;', + ' vTextureCoord = aTextureCoord;', + ' vColor = aColor;', + '}' + ].join('\n'), + // hello + [ + 'varying vec2 vTextureCoord;', + 'varying float vColor;', - 'uniform sampler2D uSampler;', - 'uniform float uAlpha;', + 'uniform sampler2D uSampler;', + 'uniform float uAlpha;', - 'void main(void){', - ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', - ' if (color.a == 0.0) discard;', - ' gl_FragColor = color;', - '}' - ].join('\n') - ); + 'void main(void){', + ' vec4 color = texture2D(uSampler, vTextureCoord) * vColor * uAlpha;', + ' if (color.a == 0.0) discard;', + ' gl_FragColor = color;', + '}' + ].join('\n') + ); - // TEMP HACK + // TEMP HACK + } } -ParticleShader.prototype = Object.create(Shader.prototype); -ParticleShader.prototype.constructor = ParticleShader; - module.exports = ParticleShader; diff --git a/src/prepare/canvas/CanvasPrepare.js b/src/prepare/canvas/CanvasPrepare.js index 89eb104..1bdf6c0 100644 --- a/src/prepare/canvas/CanvasPrepare.js +++ b/src/prepare/canvas/CanvasPrepare.js @@ -7,53 +7,55 @@ * @memberof PIXI * @param renderer {PIXI.CanvasRenderer} A reference to the current renderer */ -function CanvasPrepare() -{ +class CanvasPrepare { + constructor() + { + } + + /** + * Stub method for upload. + * @param {Function|PIXI.DisplayObject|PIXI.Container} item Either + * the container or display object to search for items to upload or + * the callback function, if items have been added using `prepare.add`. + * @param {Function} done When completed + */ + upload(displayObject, done) + { + if (typeof displayObject === 'function') + { + done = displayObject; + displayObject = null; + } + done(); + } + + /** + * Stub method for registering hooks. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + register() + { + return this; + } + + /** + * Stub method for adding items. + * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. + */ + add() + { + return this; + } + + /** + * Stub method for destroying plugin. + */ + destroy() + { + } + } -CanvasPrepare.prototype.constructor = CanvasPrepare; module.exports = CanvasPrepare; -/** - * Stub method for upload. - * @param {Function|PIXI.DisplayObject|PIXI.Container} item Either - * the container or display object to search for items to upload or - * the callback function, if items have been added using `prepare.add`. - * @param {Function} done When completed - */ -CanvasPrepare.prototype.upload = function(displayObject, done) -{ - if (typeof displayObject === 'function') - { - done = displayObject; - displayObject = null; - } - done(); -}; - -/** - * Stub method for registering hooks. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ -CanvasPrepare.prototype.register = function() -{ - return this; -}; - -/** - * Stub method for adding items. - * @return {PIXI.CanvasPrepare} Instance of plugin for chaining. - */ -CanvasPrepare.prototype.add = function() -{ - return this; -}; - -/** - * Stub method for destroying plugin. - */ -CanvasPrepare.prototype.destroy = function() -{ -}; - -core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); \ No newline at end of file +core.CanvasRenderer.registerPlugin('prepare', CanvasPrepare); diff --git a/src/prepare/webgl/WebGLPrepare.js b/src/prepare/webgl/WebGLPrepare.js index 74495b8..b37b347 100644 --- a/src/prepare/webgl/WebGLPrepare.js +++ b/src/prepare/webgl/WebGLPrepare.js @@ -7,53 +7,210 @@ * @memberof PIXI * @param renderer {PIXI.WebGLRenderer} A reference to the current renderer */ -function WebGLPrepare(renderer) -{ - /** - * Reference to the renderer. - * @type {PIXI.WebGLRenderer} - * @private - */ - this.renderer = renderer; +class WebGLPrepare { + constructor(renderer) + { + /** + * Reference to the renderer. + * @type {PIXI.WebGLRenderer} + * @private + */ + this.renderer = renderer; + + /** + * Collection of items to uploads at once. + * @type {Array<*>} + * @private + */ + this.queue = []; + + /** + * Collection of additional hooks for finding assets. + * @type {Array} + * @private + */ + this.addHooks = []; + + /** + * Collection of additional hooks for processing assets. + * @type {Array} + * @private + */ + this.uploadHooks = []; + + /** + * Callback to call after completed. + * @type {Array} + * @private + */ + this.completes = []; + + /** + * If prepare is ticking (running). + * @type {boolean} + * @private + */ + this.ticking = false; + + // Add textures and graphics to upload + this.register(findBaseTextures, uploadBaseTextures) + .register(findGraphics, uploadGraphics); + } /** - * Collection of items to uploads at once. - * @type {Array<*>} - * @private + * Upload all the textures and graphics to the GPU. + * @param {Function|PIXI.DisplayObject|PIXI.Container} item Either + * the container or display object to search for items to upload or + * the callback function, if items have been added using `prepare.add`. + * @param {Function} done When completed */ - this.queue = []; + upload(item, done) + { + if (typeof item === 'function') + { + done = item; + item = null; + } + + // If a display object, search for items + // that we could upload + if (item) + { + this.add(item); + } + + // Get the items for upload from the display + if (this.queue.length) + { + this.numLeft = WebGLPrepare.UPLOADS_PER_FRAME; + this.completes.push(done); + if (!this.ticking) + { + this.ticking = true; + SharedTicker.add(this.tick, this); + } + } + else + { + done(); + } + } /** - * Collection of additional hooks for finding assets. - * @type {Array} + * Handle tick update * @private */ - this.addHooks = []; + tick() + { + var i, len; + + // Upload the graphics + while(this.queue.length && this.numLeft > 0) + { + var item = this.queue[0]; + var uploaded = false; + for (i = 0, len = this.uploadHooks.length; i < len; i++) + { + if (this.uploadHooks[i](this.renderer, item)) + { + this.numLeft--; + this.queue.shift(); + uploaded = true; + break; + } + } + if (!uploaded) + { + this.queue.shift(); + } + } + + // We're finished + if (this.queue.length) + { + this.numLeft = WebGLPrepare.UPLOADS_PER_FRAME; + } + else + { + this.ticking = false; + SharedTicker.remove(this.tick, this); + var completes = this.completes.slice(0); + this.completes.length = 0; + for (i = 0, len = completes.length; i < len; i++) + { + completes[i](); + } + } + } /** - * Collection of additional hooks for processing assets. - * @type {Array} - * @private + * Adds hooks for finding and uploading items. + * @param {Function} [addHook] Function call that takes two parameters: `item:*, queue:Array` + function must return `true` if it was able to add item to the queue. + * @param {Function} [uploadHook] Function call that takes two parameters: `renderer:WebGLRenderer, item:*` and + * function must return `true` if it was able to handle upload of item. + * @return {PIXI.WebGLPrepare} Instance of plugin for chaining. */ - this.uploadHooks = []; + register(addHook, uploadHook) + { + if (addHook) + { + this.addHooks.push(addHook); + } + if (uploadHook) + { + this.uploadHooks.push(uploadHook); + } + return this; + } /** - * Callback to call after completed. - * @type {Array} - * @private + * Manually add an item to the uploading queue. + * @param {PIXI.DisplayObject|PIXI.Container|*} item Object to add to the queue + * @return {PIXI.WebGLPrepare} Instance of plugin for chaining. */ - this.completes = []; + add(item) + { + var i, len; + + // Add additional hooks for finding elements on special + // types of objects that + for (i = 0, len = this.addHooks.length; i < len; i++) + { + if (this.addHooks[i](item, this.queue)) + { + break; + } + } + + // Get childen recursively + if (item instanceof core.Container) + { + for (i = item.children.length - 1; i >= 0; i--) + { + this.add(item.children[i]); + } + } + return this; + } /** - * If prepare is ticking (running). - * @type {boolean} - * @private + * Destroys the plugin, don't use after this. */ - this.ticking = false; + destroy() + { + if (this.ticking) + { + SharedTicker.remove(this.tick, this); + } + this.ticking = false; + this.addHooks = null; + this.uploadHooks = null; + this.renderer = null; + this.completes = null; + this.queue = null; + } - // Add textures and graphics to upload - this.register(findBaseTextures, uploadBaseTextures) - .register(findGraphics, uploadGraphics); } /** @@ -64,164 +221,9 @@ */ WebGLPrepare.UPLOADS_PER_FRAME = 4; -WebGLPrepare.prototype.constructor = WebGLPrepare; module.exports = WebGLPrepare; /** - * Upload all the textures and graphics to the GPU. - * @param {Function|PIXI.DisplayObject|PIXI.Container} item Either - * the container or display object to search for items to upload or - * the callback function, if items have been added using `prepare.add`. - * @param {Function} done When completed - */ -WebGLPrepare.prototype.upload = function(item, done) -{ - if (typeof item === 'function') - { - done = item; - item = null; - } - - // If a display object, search for items - // that we could upload - if (item) - { - this.add(item); - } - - // Get the items for upload from the display - if (this.queue.length) - { - this.numLeft = WebGLPrepare.UPLOADS_PER_FRAME; - this.completes.push(done); - if (!this.ticking) - { - this.ticking = true; - SharedTicker.add(this.tick, this); - } - } - else - { - done(); - } -}; - -/** - * Handle tick update - * @private - */ -WebGLPrepare.prototype.tick = function() -{ - var i, len; - - // Upload the graphics - while(this.queue.length && this.numLeft > 0) - { - var item = this.queue[0]; - var uploaded = false; - for (i = 0, len = this.uploadHooks.length; i < len; i++) - { - if (this.uploadHooks[i](this.renderer, item)) - { - this.numLeft--; - this.queue.shift(); - uploaded = true; - break; - } - } - if (!uploaded) - { - this.queue.shift(); - } - } - - // We're finished - if (this.queue.length) - { - this.numLeft = WebGLPrepare.UPLOADS_PER_FRAME; - } - else - { - this.ticking = false; - SharedTicker.remove(this.tick, this); - var completes = this.completes.slice(0); - this.completes.length = 0; - for (i = 0, len = completes.length; i < len; i++) - { - completes[i](); - } - } -}; - -/** - * Adds hooks for finding and uploading items. - * @param {Function} [addHook] Function call that takes two parameters: `item:*, queue:Array` - function must return `true` if it was able to add item to the queue. - * @param {Function} [uploadHook] Function call that takes two parameters: `renderer:WebGLRenderer, item:*` and - * function must return `true` if it was able to handle upload of item. - * @return {PIXI.WebGLPrepare} Instance of plugin for chaining. - */ -WebGLPrepare.prototype.register = function(addHook, uploadHook) -{ - if (addHook) - { - this.addHooks.push(addHook); - } - if (uploadHook) - { - this.uploadHooks.push(uploadHook); - } - return this; -}; - -/** - * Manually add an item to the uploading queue. - * @param {PIXI.DisplayObject|PIXI.Container|*} item Object to add to the queue - * @return {PIXI.WebGLPrepare} Instance of plugin for chaining. - */ -WebGLPrepare.prototype.add = function(item) -{ - var i, len; - - // Add additional hooks for finding elements on special - // types of objects that - for (i = 0, len = this.addHooks.length; i < len; i++) - { - if (this.addHooks[i](item, this.queue)) - { - break; - } - } - - // Get childen recursively - if (item instanceof core.Container) - { - for (i = item.children.length - 1; i >= 0; i--) - { - this.add(item.children[i]); - } - } - return this; -}; - -/** - * Destroys the plugin, don't use after this. - */ -WebGLPrepare.prototype.destroy = function() -{ - if (this.ticking) - { - SharedTicker.remove(this.tick, this); - } - this.ticking = false; - this.addHooks = null; - this.uploadHooks = null; - this.renderer = null; - this.completes = null; - this.queue = null; -}; - -/** * Built-in hook to upload PIXI.Texture objects to the GPU * @private * @param {*} item Item to check @@ -300,4 +302,4 @@ return false; } -core.WebGLRenderer.registerPlugin('prepare', WebGLPrepare); \ No newline at end of file +core.WebGLRenderer.registerPlugin('prepare', WebGLPrepare);